+
+ >**TIP:** Read more about Kyma installation in the [official Kyma documentation](https://kyma-project.io/#/02-get-started/01-quick-install).
+
+### Run the Tests
+
+``` sh
+make -f Makefile.test-application-gateway test-gateway
+```
+
+By default, the tests clean up after themselves, removing all the previously created resources and the `test` namespace.
+
+> **CAUTION:** If the names of your existing resources are the same as the names used in the tests, running this command overrides or removes the existing resources.
+
+## Debugging
+
+### Running Locally
+
+> **CAUTION:** Because of the way it accesses the Application CRs, the test Job must run **on a cluster**.
+> Application Gateway and the mock application can both be run locally.
+
+To run the mock application locally, follow these steps:
+
+1. Change all the **targetUrl** values in the [Application CRs](../resources/charts/gateway-test/charts/test/templates/applications/) to reflect the new application URL. For example, `http://localhost:8081/v1/api/unsecure/ok`.
+2. Change all the **centralGatewayUrl** values to reflect the new Application Gateway URL. For example, `http://localhost:8080/positive-authorisation/unsecure-always-ok`.
+3. Deploy all the resources in the cluster.
+ > **NOTE:** You can omit the test Job and the Central Gateway, but it's easier to just let them fail.
+4. Build the mock application:
+
+
+
+
+ Docker
+
+
+ ```shell
+ export DOCKER_TAG="local"
+ export DOCKER_PUSH_REPOSITORY="{DOCKER_USERNAME}"
+ make image-mock-app
+ ```
+
+
+
+
+ Local
+
+
+ Change the hardcoded application port in [`config.go`](../tools/external-api-mock-app/config.go), and run:
+ ```shell
+ go build ./tools/external-api-mock-app/
+ ```
+
+
+5. Run the mock application:
+
+
+
+
+ Docker
+
+
+ ```shell
+ docker run -p 8180:8080 -p 8190:8090 -v "$PWD/resources/charts/gateway-test/charts/test/certs/positive:/etc/secret-volume:ro" -v "$PWD/resources/charts/gateway-test/charts/test/certs/negative:/etc/expired-server-cert-volume:ro" "$DOCKER_PUSH_REPOSITORY/mock-app:$DOCKER_TAG"
+ ```
+
+
+
+
+ Local
+
+
+ ```shell
+ ./external-api-mock-app
+ ```
+ > **CAUTION:** For the certificates to work, you must copy them from `./k8s/gateway-test/certs` to `/etc/secret-volume`.
+
+
+
+6. Run [Application Gateway](https://github.com/kyma-project/kyma/tree/main/components/central-application-gateway) with the `-kubeConfig {PATH_TO_YOUR_KUBECONFIG_FILE}` parameter.
+
+You can now send requests to Application Gateway, and debug its behavior locally.
+
+### Running Without Cleanup
+
+To run the tests without removing all the created resources afterwards, run them in the debugging mode.
+
+1. To start the tests in the debugging mode, run:
+
+ ``` shell
+ make disable-sidecar-for-mtls-test test-gateway-debug
+ ```
+
+2. Once you've finished debugging, run:
+
+ ``` shell
+ make clean-gateway-test enable-sidecar-after-mtls-test
+ ```
+
diff --git a/tests/docs/assets/api-auth-methods-mtls.png b/tests/docs/assets/api-auth-methods-mtls.png
new file mode 100644
index 00000000..743a1d4c
Binary files /dev/null and b/tests/docs/assets/api-auth-methods-mtls.png differ
diff --git a/tests/docs/assets/api-auth-methods.png b/tests/docs/assets/api-auth-methods.png
new file mode 100644
index 00000000..29566ba0
Binary files /dev/null and b/tests/docs/assets/api-auth-methods.png differ
diff --git a/tests/docs/assets/api-tokens-mtls.png b/tests/docs/assets/api-tokens-mtls.png
new file mode 100644
index 00000000..61fe10fc
Binary files /dev/null and b/tests/docs/assets/api-tokens-mtls.png differ
diff --git a/tests/docs/assets/api-tokens.png b/tests/docs/assets/api-tokens.png
new file mode 100644
index 00000000..386096e8
Binary files /dev/null and b/tests/docs/assets/api-tokens.png differ
diff --git a/tests/docs/assets/app-gateway-tests-architecture.svg b/tests/docs/assets/app-gateway-tests-architecture.svg
new file mode 100644
index 00000000..e148166a
--- /dev/null
+++ b/tests/docs/assets/app-gateway-tests-architecture.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/docs/assets/compass-runtime-agent-tests-architecture.svg b/tests/docs/assets/compass-runtime-agent-tests-architecture.svg
new file mode 100644
index 00000000..dc02a5e5
--- /dev/null
+++ b/tests/docs/assets/compass-runtime-agent-tests-architecture.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/docs/assets/connectivity-validator-tests-architecture.svg b/tests/docs/assets/connectivity-validator-tests-architecture.svg
new file mode 100644
index 00000000..8b852313
--- /dev/null
+++ b/tests/docs/assets/connectivity-validator-tests-architecture.svg
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/docs/assets/mock-app-mtls-spec.yaml b/tests/docs/assets/mock-app-mtls-spec.yaml
new file mode 100644
index 00000000..2852c73d
--- /dev/null
+++ b/tests/docs/assets/mock-app-mtls-spec.yaml
@@ -0,0 +1,51 @@
+openapi: 3.0.3
+info:
+ title: Mock Application for testing Application Gateway
+ description: |-
+ This is an API of Mock Application supporting Application Gateway Tests.
+ version: 1.0.11
+tags:
+ - name: OAuth tokens
+ description: Endpoints returning OAuth tokens
+ - name: CSRF
+ description: Endpoints protected by CSRF method
+ - name: No authentication
+ description: Endpoints not protected by any authentication method
+paths:
+ /v1/api/mtls-oauth/token:
+ post:
+ tags:
+ - OAuth tokens
+ summary: Returns valid OAuth token
+ operationId: oauthToken
+ responses:
+ '200':
+ description: client_id and grant_type values correct
+ '401':
+ description: Bad client_id or grant_type value
+
+ /v1/api/mtls/ok:
+ get:
+ tags:
+ - No authentication
+ summary: Returns status 200 OK if authorisation is successful
+ operationId: onBasicAuth
+ responses:
+ '200':
+ description: Authorisation successful
+ '401':
+ description: Client certificate is not valid
+ /v1/api/csrf-mtls/ok:
+ get:
+ tags:
+ - CSRF
+ summary: Returns status 200 OK if authorisation is successful
+ operationId: onCsrfOAuth
+ responses:
+ '200':
+ description: Authorisation successful
+ '401':
+ description: Client certificate is not valid
+ '403':
+ description: Username or password doesn't match or invalid CSRF token passed
+
diff --git a/tests/docs/assets/mock-app-spec.yaml b/tests/docs/assets/mock-app-spec.yaml
new file mode 100644
index 00000000..53877cb2
--- /dev/null
+++ b/tests/docs/assets/mock-app-spec.yaml
@@ -0,0 +1,186 @@
+openapi: 3.0.3
+info:
+ title: Mock Application for testing Application Gateway
+ description: |-
+ This is an API of Mock Application supporting Application Gateway Tests.
+ version: 1.0.11
+tags:
+ - name: OAuth tokens
+ description: Endpoints returning OAuth tokens
+ - name: CSRF tokens
+ description: Endpoints returning CSRF tokens
+ - name: No authentication
+ description: Endpoints not protected by any authentication method
+ - name: Basic Authentication
+ description: Endpoints protected by Basic Authentication
+ - name: OAuth
+ description: Endpoints protected by OAuth method expecting valid OAuth token
+ - name: Basic Authentication and CSRF token
+ description: Endpoints protected by Basic Authentication and CSRF methods
+ - name: OAuth and CSRF
+ description: Endpoints protected by OAuth and CSRF methods
+ - name: Basic Authentication and request parameters
+ description: Endpoints protected by Basic Authentication and additional request parameters
+paths:
+ /v1/api/oauth/token:
+ post:
+ tags:
+ - OAuth tokens
+ summary: Returns valid OAuth token
+ operationId: oauthToken
+ responses:
+ '200':
+ description: client_id, client_secret and grant_type values correct
+ '401':
+ description: Bad client_id, client_secret or grant_type value
+ /v1/api/oauth/bad-token:
+ post:
+ tags:
+ - OAuth tokens
+ summary: Returns invalid OAuth token
+ operationId: oauthBadToken
+ responses:
+ '200':
+ description: client_id, client_secret and grant_type values correct
+ '401':
+ description: Bad client_id, client_secret or grant_type value
+ /v1/api/csrf/token:
+ get:
+ tags:
+ - CSRF tokens
+ summary: Returns valid CSRF token
+ operationId: csrfToken
+ responses:
+ '200':
+ description: Token generated successfully
+ /v1/api/csrf/bad-token:
+ get:
+ tags:
+ - CSRF tokens
+ summary: Returns invalid CSRF token
+ responses:
+ '200':
+ description: Token generated successfully
+
+
+ /v1/api/unsecure/ok:
+ get:
+ tags:
+ - No authentication
+ summary: Returns status 200 OK
+ operationId: okNoAuth
+ responses:
+ '200':
+ description: Successful operation
+ /v1/api/unsecure/echo:
+ put:
+ tags:
+ - No authentication
+ summary: Responds with request body sent to the endpoint
+ operationId: echoNoAuthPut
+ responses:
+ '200':
+ description: Successful operation
+ post:
+ tags:
+ - No authentication
+ summary: Responds with request body sent to the endpoint
+ operationId: echoNoAuthPost
+ responses:
+ '200':
+ description: Successful operation
+ delete:
+ tags:
+ - No authentication
+ summary: Responds with request body sent to the endpoint
+ operationId: echoNoAuthDelete
+ responses:
+ '200':
+ description: Successful operation
+ /v1/api/unsecure/code/{code}:
+ get:
+ tags:
+ - No authentication
+ parameters:
+ - in: path
+ name: code
+ schema:
+ type: integer
+ required: true
+ summary: Responds with status code specified in the {code} parameter
+ operationId: codeNoAuth
+ responses:
+ '200':
+ description: Successful operation
+ /v1/api/unsecure/timeout:
+ get:
+ tags:
+ - No authentication
+ summary: Sleeps for 2 minutes before responding
+ operationId: timeoutNoAuth
+ responses:
+ '200':
+ description: Successful operation
+ /v1/api/basic/ok:
+ get:
+ tags:
+ - Basic Authentication
+ summary: Returns status 200 OK if authentication is successful
+ operationId: onBasicAuth
+ responses:
+ '200':
+ description: Authentication successful
+ '403':
+ description: Username or password doesn't match
+ /v1/api/oauth/ok:
+ get:
+ tags:
+ - OAuth
+ summary: Returns status 200 OK if authentication is successful
+ operationId: onOAuth
+ responses:
+ '200':
+ description: Authentication successful
+ '401':
+ description: Authorization header missing or contains invalid token
+
+ /v1/api/csrf-basic/ok:
+ get:
+ tags:
+ - Basic Authentication and CSRF token
+ summary: Returns status 200 OK if authentication is successful
+ operationId: onCsrfBasic
+ responses:
+ '200':
+ description: Authentication successful
+ '403':
+ description: Username or password doesn't match or invalid CSRF token passed
+
+ /v1/api/csrf-oauth/ok:
+ get:
+ tags:
+ - OAuth and CSRF
+ summary: Returns status 200 OK if authentication is successful
+ operationId: onCsrfOAuth
+ responses:
+ '200':
+ description: Authentication successful
+ '401':
+ description: Authorization header missing or contains invalid token
+ '403':
+ description: Username or password doesn't match or invalid CSRF token passed
+
+ /v1/api/request-parameters-basic/ok:
+ get:
+ tags:
+ - Basic Authentication and request parameters
+ summary: Returns status 200 OK if authentication is successful
+ operationId: onRequestParamsBasic
+ responses:
+ '200':
+ description: Authentication successful
+ '400':
+ description: Expected headers and request params not passed
+ '403':
+ description: Username or password doesn't match
+
diff --git a/tests/docs/compass-runtime-agent-tests.md b/tests/docs/compass-runtime-agent-tests.md
new file mode 100644
index 00000000..afc21a24
--- /dev/null
+++ b/tests/docs/compass-runtime-agent-tests.md
@@ -0,0 +1,138 @@
+# Compass Runtime Agent
+
+**Table of Contents**
+
+- [Compass Runtime Agent](#compass-runtime-agent)
+ - [Design and Architecture](#design-and-architecture)
+ - [Building](#building)
+ - [Running](#running)
+ - [Deploy a Kyma Cluster Locally](#deploy-a-kyma-cluster-locally)
+ - [Test Setup - Compass Runtime Agent Configuration](#test-setup---compass-runtime-agent-configuration)
+ - [Run the Tests](#run-the-tests)
+ - [Debugging](#debugging)
+ - [Running Without Cleanup](#running-without-cleanup)
+ - [Debugging in the IDE](#debugging-in-the-ide)
+
+## Design and Architecture
+
+The tests consist of:
+- [Test resources](../resources/charts/compass-runtime-agent-test/) used to perform the test
+- [Test runner](../test/application-connectivity-validator/) with all the test cases
+
+The tests are executed as a Kubernetes Job in a Kyma cluster where the tested Compass Runtime Agent is installed. The test Job is deployed in the `test` namespace.
+
+![Compass Runtime Agent tests architecture](assets/compass-runtime-agent-tests-architecture.svg)
+
+The interactions between components are the following:
+
+1. Compass Runtime Agent periodically fetches certificates from Compass Connector.
+2. Compass Runtime Agent periodically fetches applications from Compass Director.
+3. Compass Runtime Agent Test sends GraphQL mutations to Compass Director to create, modify, or delete Applications.
+4. Compass Runtime Agent Test verifies whether corresponding Application CRs were created, modified, or deleted.
+5. Compass Runtime Agent Test verifies whether the Secret with certificates used for communication with Director was created.
+6. Compass Runtime Agent Test verifies whether the Secret with the CA root certificate used by Istio Gateway was created.
+7. Compass Runtime Agent Test verifies the content of the CompassConnection CR.
+
+## Building
+
+Pipelines build the Compass Runtime Agent test using the **release** target from the `Makefile`.
+
+To build **and push** the Docker images of the tests, run:
+
+```bash
+./scripts/local-build.sh {DOCKER_TAG} {DOCKER_PUSH_REPOSITORY}
+```
+
+This builds the following images:
+- `{DOCKER_PUSH_REPOSITORY}/compass-runtime-agent-test:{DOCKER_TAG}`
+
+## Running
+
+Tests can be run on any Kyma cluster with Compass Runtime Agent.
+
+Pipelines run the tests using the **test-compass-runtime-agent** target from the `Makefile`.
+
+### Deploy a Kyma Cluster Locally
+
+1. Provision a local Kubernetes cluster with k3d:
+ ```bash
+ kyma provision k3d
+ ```
+
+2. Install the minimal set of components required to run Compass Runtime Agent **for Kyma SKR (Compass mode)**:
+
+ ```bash
+ kyma deploy --components-file ./resources/installation-config/mini-kyma-skr.yaml --value global.disableLegacyConnectivity=true --value compassRuntimeAgent.director.proxy.insecureSkipVerify=true
+ ```
+
+ >**TIP:** Read more about [Kyma installation](https://kyma-project.io/#/02-get-started/01-quick-install).
+
+### Test Setup - Compass Runtime Agent Configuration
+
+The [`values.yaml`](../resources/charts/compass-runtime-agent-test/values.yaml) file contains environment variables that are used in the Compass Runtime Agent tests. These values can be modified as needed.
+
+- **APP_DIRECTOR_URL** - Compass Director URL
+- **APP_TESTING_TENANT** - Tenant used in GraphQL calls
+- **APP_SKIP_DIRECTOR_CERT_VERIFICATION** - Skip certificate verification on the Director side
+- **APP_OAUTH_CREDENTIALS_SECRET_NAME** - Secret name for Compass OAuth credentials
+- **APP_OAUTH_CREDENTIALS_NAMESPACE** - Namespace for Compass OAuth credentials
+
+### Run the Tests
+
+1. Before running the test export the following environment variables
+ - **COMPASS_HOST** - host running Compass
+ - **COMPASS_CLIENT_ID** - client ID used for fetching authorization tokens
+ - **COMPASS_CLIENT_SECRET** - client Secret used for fetching authorization tokens
+
+2. To start the tests, run:
+
+ ```bash
+ make test-compass-runtime-agent
+ ```
+
+By default, the tests clean up after themselves, removing all the previously created resources and the `test` namespace.
+
+> **CAUTION:** If the names of your existing resources are the same as the names used in the tests, running this command overrides or removes the existing resources.
+
+## Debugging
+
+### Running Without Cleanup
+
+To run the tests without removing all the created resources afterwards, run them in the debugging mode.
+
+1. To start the tests in the debugging mode, run:
+
+ ```bash
+ make test-compass-runtime-agent-debug
+ ```
+
+2. Once you've finished debugging, run:
+
+ ```bash
+ make clean-test-compass-runtime-agent-test
+ ```
+
+### Debugging in the IDE
+
+To run the test in your IDE, perform the following steps.
+
+1. To prepare the cluster for debugging, run the test without cleanup:
+
+ ```bash
+ make test-compass-runtime-agent-debug
+ ```
+
+2. Before starting debugger in your IDE export the following environment variables:
+ - `KUBECONFIG={Your cluster kubeconfig}`
+ - `APP_DIRECTOR_URL=https://compass-gateway-auth-oauth.{COMPASS_HOST}/director/graphql`
+ - `APP_TESTING_TENANT=3e64ebae-38b5-46a0-b1ed-9ccee153a0ae`
+ - `APP_OAUTH_CREDENTIALS_SECRET_NAME=oauth-compass-credentials`
+ - `APP_OAUTH_CREDENTIALS_NAMESPACE=test`
+
+3. Start the debugging session.
+
+4. Once you've finished debugging, run:
+
+ ```bash
+ make clean-test-compass-runtime-agent-test
+ ```
\ No newline at end of file
diff --git a/tests/go.mod b/tests/go.mod
new file mode 100644
index 00000000..1b27f217
--- /dev/null
+++ b/tests/go.mod
@@ -0,0 +1,90 @@
+module github.com/kyma-project/kyma/tests/components/application-connector
+
+go 1.18
+
+require (
+ github.com/avast/retry-go v3.0.0+incompatible
+ github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6
+ github.com/google/uuid v1.3.0
+ github.com/gorilla/mux v1.8.0
+ github.com/hashicorp/go-multierror v1.1.1
+ github.com/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0
+ github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61
+ github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1
+ github.com/matryer/is v1.4.0
+ github.com/pkg/errors v0.9.1
+ github.com/sirupsen/logrus v1.9.0
+ github.com/stretchr/testify v1.8.1
+ github.com/vrischmann/envconfig v1.3.0
+ k8s.io/api v0.26.0
+ k8s.io/apimachinery v0.26.0
+ k8s.io/client-go v0.26.0
+)
+
+require (
+ github.com/99designs/gqlgen v0.11.3 // indirect
+ github.com/Masterminds/goutils v1.1.1 // indirect
+ github.com/Masterminds/semver/v3 v3.1.1 // indirect
+ github.com/Masterminds/sprig/v3 v3.2.2 // indirect
+ github.com/agnivade/levenshtein v1.1.0 // indirect
+ github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
+ github.com/davecgh/go-spew v1.1.1 // indirect
+ github.com/emicklei/go-restful/v3 v3.9.0 // indirect
+ github.com/evanphx/json-patch v4.12.0+incompatible // indirect
+ github.com/go-logr/logr v1.2.3 // indirect
+ github.com/go-openapi/jsonpointer v0.19.5 // indirect
+ github.com/go-openapi/jsonreference v0.20.0 // indirect
+ github.com/go-openapi/swag v0.19.15 // indirect
+ github.com/go-ozzo/ozzo-validation/v4 v4.3.0 // indirect
+ github.com/gogo/protobuf v1.3.2 // indirect
+ github.com/golang/protobuf v1.5.2 // indirect
+ github.com/google/gnostic v0.5.7-v3refs // indirect
+ github.com/google/go-cmp v0.5.9 // indirect
+ github.com/google/gofuzz v1.2.0 // indirect
+ github.com/hashicorp/errwrap v1.1.0 // indirect
+ github.com/huandu/xstrings v1.3.2 // indirect
+ github.com/imdario/mergo v0.3.12 // indirect
+ github.com/josharian/intern v1.0.0 // indirect
+ github.com/json-iterator/go v1.1.12 // indirect
+ github.com/mailru/easyjson v0.7.6 // indirect
+ github.com/mitchellh/copystructure v1.1.2 // indirect
+ github.com/mitchellh/reflectwalk v1.0.1 // indirect
+ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
+ github.com/modern-go/reflect2 v1.0.2 // indirect
+ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+ github.com/onrik/logrus v0.9.0 // indirect
+ github.com/pmezard/go-difflib v1.0.0 // indirect
+ github.com/shopspring/decimal v1.2.0 // indirect
+ github.com/spf13/cast v1.4.1 // indirect
+ github.com/spf13/pflag v1.0.5 // indirect
+ github.com/stretchr/objx v0.5.0 // indirect
+ github.com/vektah/gqlparser/v2 v2.1.0 // indirect
+ github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
+ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
+ github.com/xeipuuv/gojsonschema v1.2.0 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/net v0.10.0 // indirect
+ golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
+ golang.org/x/sys v0.15.0 // indirect
+ golang.org/x/term v0.15.0 // indirect
+ golang.org/x/text v0.14.0 // indirect
+ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
+ google.golang.org/appengine v1.6.7 // indirect
+ google.golang.org/protobuf v1.28.1 // indirect
+ gopkg.in/inf.v0 v0.9.1 // indirect
+ gopkg.in/yaml.v2 v2.4.0 // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
+ k8s.io/klog/v2 v2.80.1 // indirect
+ k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect
+ k8s.io/utils v0.0.0-20221107191617-1a15be271d1d // indirect
+ sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect
+ sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect
+ sigs.k8s.io/yaml v1.3.0 // indirect
+)
+
+replace (
+ golang.org/x/crypto => golang.org/x/crypto v0.17.0
+ golang.org/x/net => golang.org/x/net v0.17.0
+ golang.org/x/text => golang.org/x/text v0.12.0
+ google.golang.org/protobuf => google.golang.org/protobuf v1.33.0
+)
diff --git a/tests/go.sum b/tests/go.sum
new file mode 100644
index 00000000..b6c0f238
--- /dev/null
+++ b/tests/go.sum
@@ -0,0 +1,557 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
+cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
+cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
+cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
+cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
+cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
+cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
+cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
+cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
+cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
+cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
+cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
+cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
+cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
+cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
+cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/99designs/gqlgen v0.11.3 h1:oFSxl1DFS9X///uHV3y6CEfpcXWrDUxVblR4Xib2bs4=
+github.com/99designs/gqlgen v0.11.3/go.mod h1:RgX5GRRdDWNkh4pBrdzNpNPFVsdoUFY2+adM6nb1N+4=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI=
+github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU=
+github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
+github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
+github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8=
+github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk=
+github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
+github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs=
+github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
+github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
+github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
+github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
+github.com/asaskevich/govalidator v0.0.0-20200108200545-475eaeb16496/go.mod h1:oGkLhpf+kjZl6xBf758TQhh5XrAeiJv/7FRz/2spLIg=
+github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ=
+github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
+github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
+github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
+github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
+github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
+github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE=
+github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/evanphx/json-patch v4.12.0+incompatible h1:4onqiflcdA9EOZ4RxV643DvftH5pOlLGNtQ5lPWQu84=
+github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
+github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
+github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6 h1:R/ypabUA7vskKTRSlgP6rMUHTU6PBRgIcHVSU9qQ6qM=
+github.com/go-http-utils/logger v0.0.0-20161128092850-f3a42dcdeae6/go.mod h1:CpBLxS3WrxouNECP/Y1A3i6qDnUYs8BvcXjgOW4Vqcw=
+github.com/go-logr/logr v1.2.0/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-logr/logr v1.2.3 h1:2DntVwHkVopvECVRSlL5PSo9eG+cAkDCuckLubN+rq0=
+github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
+github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY=
+github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg=
+github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA=
+github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo=
+github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
+github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
+github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ=
+github.com/go-ozzo/ozzo-validation/v4 v4.3.0 h1:byhDUpfEwjsVQb1vBunvIjh2BHQ9ead57VkAEY4V+Es=
+github.com/go-ozzo/ozzo-validation/v4 v4.3.0/go.mod h1:2NKgrcHl3z6cJs+3Oo940FPRiTzuqKbvfrL2RxCj6Ew=
+github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
+github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/gnostic v0.5.7-v3refs h1:FhTMOKj2VhjpouxvWJAV1TL304uMlb9zcDqkl6cEI54=
+github.com/google/gnostic v0.5.7-v3refs/go.mod h1:73MKFl6jIHelAJNaBGFzt3SPtZULs9dYrGFt8OiIsHQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
+github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
+github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
+github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
+github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw=
+github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
+github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
+github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
+github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA=
+github.com/jmoiron/sqlx v1.3.4 h1:wv+0IJZfL5z0uZoUjlpKgHkgaFSYD+r9CfrXjEXsO7w=
+github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
+github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
+github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0 h1:5pDxnqY8TK59Zp+9PqsC8uEG6ZeeURJKHxUo19ez+jY=
+github.com/kyma-incubator/compass/components/director v0.0.0-20220126084901-92232f5eced0/go.mod h1:62mrVWDkGdPxAqj+X97FKiA/e8jYSZ/MARgzoThk9AU=
+github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61 h1:iviPUIyUTMKA322amhFURlXbIbj9NrojpvJFDI+DtnQ=
+github.com/kyma-project/kyma/components/central-application-gateway v0.0.0-20230130154909-4c81ab2cee61/go.mod h1:NL5E+cv7oyD8xJtDywLrHnkublvqifMBt5HFdw94adc=
+github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1 h1:zhIQX99vZIS5nlWIQZE6nIVB3w7W+vSgE5r9+VxLZGE=
+github.com/kyma-project/kyma/components/compass-runtime-agent v0.0.0-20220927112044-a548531152a1/go.mod h1:D80/HUyVanrVfAcUOt8xRWp5oZwd1IK4SAg0A9Hlj+8=
+github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
+github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
+github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
+github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
+github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
+github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE=
+github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
+github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
+github.com/mitchellh/copystructure v1.1.2 h1:Th2TIvG1+6ma3e/0/bopBKohOTY7s4dA8V2q4EUcBJ0=
+github.com/mitchellh/copystructure v1.1.2/go.mod h1:EBArHfARyrSWO/+Wyr9zwEkc6XMFB9XyNgFNmRkZZU4=
+github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/mitchellh/reflectwalk v1.0.1 h1:FVzMWA5RllMAKIdUSC8mdWo3XtwoecrH79BY70sEEpE=
+github.com/mitchellh/reflectwalk v1.0.1/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
+github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
+github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/onrik/logrus v0.9.0 h1:oT7VstCUxWBoX7fswYK61fi9bzRBSpROq5CR2b7wxQo=
+github.com/onrik/logrus v0.9.0/go.mod h1:qfe9NeZVAJfIxviw3cYkZo3kvBtLoPRJriAO8zl7qTk=
+github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs=
+github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys=
+github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74=
+github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
+github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
+github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
+github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw=
+github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
+github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
+github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
+github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/tidwall/gjson v1.12.1 h1:ikuZsLdhr8Ws0IdROXUS1Gi4v9Z4pGqpX/CvJkxvfpo=
+github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
+github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
+github.com/tidwall/sjson v1.2.4 h1:cuiLzLnaMeBhRmEv00Lpk3tkYrcxpmbU81tAY4Dw0tc=
+github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
+github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
+github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
+github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns=
+github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms=
+github.com/vrischmann/envconfig v1.3.0 h1:4XIvQTXznxmWMnjouj0ST5lFo/WAYf5Exgl3x82crEk=
+github.com/vrischmann/envconfig v1.3.0/go.mod h1:bbvxFYJdRSpXrhS63mBFtKJzkDiNkyArOLXtY6q0kuI=
+github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
+github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
+github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
+github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
+github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
+github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
+golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
+golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
+golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
+golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b h1:clP8eMhB30EHdc0bd2Twtq6kgU7yl5ub2cQLSdrv1Dg=
+golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
+golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
+golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc=
+golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
+golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
+golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
+golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
+google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
+google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
+google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
+google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
+google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
+google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
+google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/genproto v0.0.0-20201019141844-1ed22bb0c154/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
+google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
+google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
+k8s.io/api v0.26.0 h1:IpPlZnxBpV1xl7TGk/X6lFtpgjgntCg8PJ+qrPHAC7I=
+k8s.io/api v0.26.0/go.mod h1:k6HDTaIFC8yn1i6pSClSqIwLABIcLV9l5Q4EcngKnQg=
+k8s.io/apimachinery v0.26.0 h1:1feANjElT7MvPqp0JT6F3Ss6TWDwmcjLypwoPpEf7zg=
+k8s.io/apimachinery v0.26.0/go.mod h1:tnPmbONNJ7ByJNz9+n9kMjNP8ON+1qoAIIC70lztu74=
+k8s.io/client-go v0.26.0 h1:lT1D3OfO+wIi9UFolCrifbjUUgu7CpLca0AD8ghRLI8=
+k8s.io/client-go v0.26.0/go.mod h1:I2Sh57A79EQsDmn7F7ASpmru1cceh3ocVT9KlX2jEZg=
+k8s.io/klog/v2 v2.80.1 h1:atnLQ121W371wYYFawwYx1aEY2eUfs4l3J72wtgAwV4=
+k8s.io/klog/v2 v2.80.1/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 h1:+70TFaan3hfJzs+7VK2o+OGxg8HsuBr/5f6tVAjDu6E=
+k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280/go.mod h1:+Axhij7bCpeqhklhUTe3xmOn6bWxolyZEeyaFpjGtl4=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d h1:0Smp/HP1OH4Rvhe+4B8nWGERtlqAGSftbSbbmm45oFs=
+k8s.io/utils v0.0.0-20221107191617-1a15be271d1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
+rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
+rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 h1:iXTIw73aPyC+oRdyqqvVJuloN1p0AC/kzH07hu3NE+k=
+sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3 h1:PRbqxJClWWYMNV1dhaG4NsibJbArud9kFxnAMREiWFE=
+sigs.k8s.io/structured-merge-diff/v4 v4.2.3/go.mod h1:qjx8mGObPmV2aSZepjQjbmb2ihdVs8cGKBraizNC69E=
+sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
+sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
+sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
+sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=
diff --git a/tests/hack/ci/.srl b/tests/hack/ci/.srl
new file mode 100644
index 00000000..0ef5e90b
--- /dev/null
+++ b/tests/hack/ci/.srl
@@ -0,0 +1 @@
+8FC09AB8ECD2BADC
diff --git a/tests/hack/ci/Makefile b/tests/hack/ci/Makefile
new file mode 100644
index 00000000..51ee6a24
--- /dev/null
+++ b/tests/hack/ci/Makefile
@@ -0,0 +1,150 @@
+K3D_URL=https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh
+
+PROJECT_ROOT ?= ../../..
+CLUSTER_NAME ?= kyma
+REGISTRY_PORT ?= 5001
+REGISTRY_NAME ?= ${CLUSTER_NAME}-registry
+
+# Operating system architecture
+OS_ARCH ?= $(shell uname -m)
+
+# Operating system type
+OS_TYPE ?= $(shell uname)
+
+.PHONY: application-connector-module-image
+application-connector-module-image:
+ @echo "::group::application-connector-module-image"
+ @make -C ${PROJECT_ROOT}/hack/common module-image
+ @echo "::endgroup::"
+
+.PHONY: application-connector-deploy
+application-connector-deploy:
+ @echo "::group::application-connector-deploy"
+ @make -C ${PROJECT_ROOT}/hack/common deploy \
+ IMG=k3d-${REGISTRY_NAME}:${REGISTRY_PORT}/${MANAGER_IMAGE_NAME}:${MANAGER_IMAGE_TAG}
+ @echo "::endgroup::"
+
+.PHONY: install-application-connector
+install-application-connector: application-connector-module-image application-connector-deploy apply-appcon
+ @echo "::group::install-application-connector"
+ kubectl wait -n kyma-system \
+ applicationconnectors/applicationconnector-sample \
+ --for=jsonpath='{.status.state}'=Ready \
+ --timeout=300s
+ @echo "::endgroup::"
+
+.PHONY: install-istio
+install-istio: create-kyma-system-ns
+ @echo "::group::install-istio"
+ kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-manager.yaml
+ kubectl apply -f https://github.com/kyma-project/istio/releases/latest/download/istio-default-cr.yaml
+ kubectl wait -n kyma-system istios/default --for=jsonpath='{.status.state}'=Ready --timeout=300s
+ @echo "::endgroup::"
+
+.PHONY: create-kyma-system-ns
+create-kyma-system-ns: ## Create kyma-system namespace.
+ @echo "::group::create-kyma-system-ns"
+ kubectl create ns kyma-system
+ kubectl label namespaces kyma-system istio-injection=enabled --overwrite=true
+ @echo "::endgroup::"
+
+.PHONY: create-k3d
+create-k3d: ## Create k3d with kyma CRDs.
+ @echo "::group::create-k3d"
+ k3d cluster create ${CLUSTER_NAME} \
+ --api-port 6550 \
+ -p 8080:80@loadbalancer \
+ -p 8443:443@loadbalancer \
+ -p 9090-9099:9090-9099@loadbalancer \
+ --agents 2 \
+ --registry-create k3d-${REGISTRY_NAME}:${REGISTRY_PORT}
+ @echo "::endgroup::"
+
+.PHONY: apply-appcon
+apply-appcon: ## Apply the k3d application-connector CR
+ @echo "::group::apply-appcon"
+ @make -C ${PROJECT_ROOT}/hack/common apply-appcon
+ @echo "::endgroup::"
+
+.PHONY: apply-appcon-crd
+apply-appcon-crd: ## Apply the application-connector CRD
+ @echo "::group::apply-appcon-crd"
+ kubectl apply -f ${PROJECT_ROOT}/tests/hack/ci/deps/applications.applicationconnector.crd.yaml
+ @echo "::endgroup::"
+
+.PHONY: apply-compass-connection-crd
+apply-compass-connection-crd: ## Apply the compass-connection CRD
+ @echo "::group::apply-compas-connection-crd"
+ kubectl apply -f ${PROJECT_ROOT}/tests/hack/ci/deps/compass-connection.crd.yaml
+ @echo "::endgroup::"
+
+.PHONY: gateway-tests
+gateway-tests:
+ @echo "::group::gateway-tests"
+ @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-gateway test
+ @echo "::endgroup::"
+
+.PHONY: k3d-gateway-tests
+k3d-gateway-tests: create-k3d \
+ install-istio \
+ install-application-connector \
+ apply-appcon-crd \
+ gateway-tests
+
+.PHONY: patch-validator
+patch-validator:
+ @echo "::group::patch-validator"
+ @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-conn-validator patch-validator
+ @echo "::endgroup::"
+
+.PHONY: patch-compass-runtime-agent
+patch-compass-runtime-agent:
+ @echo "::group::patch-compass-runtime-agent"
+ @make -f ${PROJECT_ROOT}/tests/Makefile.test-compass-runtime-agent patch-compass-runtime-agent
+ @echo "::endgroup::"
+
+.PHONY: validator-tests
+validator-tests:
+ @echo "::group::validator-tests"
+ @make -f ${PROJECT_ROOT}/tests/Makefile.test-application-conn-validator test
+ @echo "::endgroup::"
+
+.PHONY: k3d-validator-tests
+k3d-validator-tests: patch-validator \
+ create-k3d \
+ install-istio \
+ install-application-connector \
+ apply-appcon-crd \
+ validator-tests
+
+.PHONY: k3d-agent-tests
+k3d-agent-tests: create-k3d \
+ install-istio \
+ patch-compass-runtime-agent \
+ install-application-connector \
+ apply-compass-connection-crd \
+ apply-appcon-crd \
+ compass-runtime-agent-test-image \
+ agent-tests
+
+.PHONY: agent-tests
+agent-tests:
+ @echo "::group::agent-tests"
+ @make -f ${PROJECT_ROOT}/tests/Makefile.test-compass-runtime-agent test
+ @echo "::endgroup::"
+
+.PHONY: compass-runtime-agent-test-image
+compass-runtime-agent-test-image:
+ @echo "::group::compass-runtime-agent-test-image"
+ @make -C ${PROJECT_ROOT}/tests compass-runtime-agent-test-image \
+ DOCKER_PUSH_REPOSITORY=localhost:${REGISTRY_PORT} \
+ DOCKER_TAG=002
+ @echo "::endgroup::"
+
+.PHONY: install-compass-runtime-agent
+install-compass-runtime-agent: compass-runtime-agent-test-image
+ @echo "::group::install-compass-runtime-agent"
+ @helm template ${PROJECT_ROOT}/tests/hack/ci/resources/charts/compass-runtime-agent \
+ -n kyma-system \
+ | kubectl apply -f -
+ @echo "::endgroup::"
diff --git a/tests/hack/ci/deps/applications.applicationconnector.crd.yaml b/tests/hack/ci/deps/applications.applicationconnector.crd.yaml
new file mode 100644
index 00000000..1a7b6518
--- /dev/null
+++ b/tests/hack/ci/deps/applications.applicationconnector.crd.yaml
@@ -0,0 +1,183 @@
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ "helm.sh/resource-policy": keep
+ name: applications.applicationconnector.kyma-project.io
+spec:
+ group: applicationconnector.kyma-project.io
+ preserveUnknownFields: false
+ versions:
+ - name: v1alpha1
+ served: true
+ storage: true
+ schema:
+ openAPIV3Schema:
+ type: object
+ properties:
+ spec:
+ properties:
+ compassMetadata:
+ type: object
+ required:
+ - "authentication"
+ properties:
+ applicationId:
+ type: string
+ authentication:
+ type: object
+ required:
+ - "clientIds"
+ properties:
+ clientIds:
+ type: array
+ items:
+ type: string
+ accessLabel:
+ type: string
+ maxLength: 63
+ pattern: '^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$'
+ description:
+ type: string
+ skipInstallation:
+ type: boolean
+ skipVerify:
+ type: boolean
+ encodeUrl:
+ type: boolean
+ default: true
+ labels:
+ nullable: true
+ additionalProperties:
+ type: string
+ type: object
+ tenant:
+ type: string
+ group:
+ type: string
+ tags:
+ nullable: true
+ description: New fields used by V2 version
+ items:
+ type: string
+ type: array
+ displayName:
+ type: string
+ providerDisplayName:
+ type: string
+ longDescription:
+ type: string
+ services:
+ type: array
+ items:
+ type: object
+ required:
+ - "id"
+ - "name"
+ - "displayName"
+ - "providerDisplayName"
+ - "description"
+ - "entries"
+ properties:
+ id:
+ type: string
+ name:
+ type: string
+ identifier:
+ type: string
+ labels:
+ nullable: true
+ additionalProperties:
+ type: string
+ description: Deprecated
+ type: object
+ displayName:
+ type: string
+ description:
+ type: string
+ longDescription:
+ type: string
+ providerDisplayName:
+ type: string
+ authCreateParameterSchema:
+ description: New fields used by V2 version
+ type: string
+ entries:
+ type: array
+ items:
+ type: object
+ required:
+ - "type"
+ properties:
+ apiType:
+ type: string
+ type:
+ type: string
+ enum:
+ - "API"
+ - "Events"
+ gatewayUrl:
+ type: string
+ centralGatewayUrl:
+ type: string
+ accessLabel:
+ type: string
+ maxLength: 63
+ pattern: '^([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$'
+ targetUrl:
+ type: string
+ id:
+ type: string
+ name:
+ description: New fields used by V2 version
+ type: string
+ requestParametersSecretName:
+ type: string
+ specificationUrl:
+ type: string
+ credentials:
+ type: object
+ required:
+ - "type"
+ - "secretName"
+ properties:
+ type:
+ type: string
+ secretName:
+ type: string
+ authenticationUrl:
+ type: string
+ csrfInfo:
+ type: object
+ required:
+ - "tokenEndpointURL"
+ properties:
+ tokenEndpointURL:
+ type: string
+ tags:
+ type: array
+ items:
+ type: string
+ type: object
+ status:
+ properties:
+ installationStatus:
+ description: Represents the status of Application release installation
+ properties:
+ description:
+ type: string
+ status:
+ type: string
+ required:
+ - status
+ type: object
+ required:
+ - installationStatus
+ type: object
+ scope: Cluster
+ names:
+ plural: applications
+ singular: application
+ kind: Application
+ shortNames:
+ - app
diff --git a/tests/hack/ci/deps/compass-connection.crd.yaml b/tests/hack/ci/deps/compass-connection.crd.yaml
new file mode 100644
index 00000000..aae2958a
--- /dev/null
+++ b/tests/hack/ci/deps/compass-connection.crd.yaml
@@ -0,0 +1,144 @@
+---
+apiVersion: apiextensions.k8s.io/v1
+kind: CustomResourceDefinition
+metadata:
+ annotations:
+ "helm.sh/resource-policy": keep
+ name: compassconnections.compass.kyma-project.io
+spec:
+ group: compass.kyma-project.io
+ names:
+ kind: CompassConnection
+ listKind: CompassConnectionList
+ plural: compassconnections
+ singular: compassconnection
+ scope: Cluster
+ versions:
+ - name: v1alpha1
+ schema:
+ openAPIV3Schema:
+ properties:
+ apiVersion:
+ description: 'APIVersion defines the versioned schema of this representation
+ of an object. Servers should convert recognized schemas to the latest
+ internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+ type: string
+ kind:
+ description: 'Kind is a string value representing the REST resource this
+ object represents. Servers may infer this from the endpoint the client
+ submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+ type: string
+ metadata:
+ type: object
+ spec:
+ properties:
+ managementInfo:
+ properties:
+ connectorUrl:
+ description: 'URL used for maintaining the secure connection.'
+ type: string
+ directorUrl:
+ description: 'URL used for fetching Applications.'
+ type: string
+ required:
+ - connectorUrl
+ - directorUrl
+ type: object
+ refreshCredentialsNow:
+ description: 'If set to `true`, ignores certificate expiration date and refreshes in the next round.'
+ type: boolean
+ resyncNow:
+ description: 'If set to `true`, ignores `APP_MINIMAL_COMPASS_SYNC_TIME` and syncs in the next round.'
+ type: boolean
+ required:
+ - managementInfo
+ type: object
+ status:
+ properties:
+ connectionState:
+ type: string
+ connectionStatus:
+ description: 'Represents the status of the connection to
+ Compass.'
+ properties:
+ certificateStatus:
+ description: 'Specifies the certificate issue and expiration dates.'
+ properties:
+ acquired:
+ description: 'Specifies when the certificate was acquired.'
+ format: date-time
+ nullable: true
+ type: string
+ notAfter:
+ description: 'Specifies when the certificate stops being valid.'
+ format: date-time
+ nullable: true
+ type: string
+ notBefore:
+ description: 'Specifies when the certificate becomes valid.'
+ format: date-time
+ nullable: true
+ type: string
+ type: object
+ error:
+ type: string
+ established:
+ description: 'Specifies when the connection was established.'
+ format: date-time
+ nullable: true
+ type: string
+ lastSuccess:
+ description: 'Specifies the date of the last successful synchronization with the Connector.'
+ format: date-time
+ nullable: true
+ type: string
+ lastSync:
+ description: 'Specifies the date of the last synchronization attempt.'
+ format: date-time
+ nullable: true
+ type: string
+ renewed:
+ description: 'Specifies the date of the last certificate renewal.'
+ format: date-time
+ nullable: true
+ type: string
+ required:
+ - certificateStatus
+ type: object
+ synchronizationStatus:
+ description: 'Provides the status of the synchronization with the Director.'
+ nullable: true
+ properties:
+ error:
+ type: string
+ lastAttempt:
+ description: 'Specifies the date of the last synchronization attempt with the Director.'
+ format: date-time
+ nullable: true
+ type: string
+ lastSuccessfulApplication:
+ description: 'Specifies the date of the last successful application of resources fetched from Compass.'
+ format: date-time
+ nullable: true
+ type: string
+ lastSuccessfulFetch:
+ description: 'Specifies the date of the last successful fetch of resources from the Director.'
+ format: date-time
+ nullable: true
+ type: string
+ type: object
+ required:
+ - connectionState
+ - connectionStatus
+ type: object
+ required:
+ - spec
+ type: object
+ served: true
+ storage: true
+status:
+ acceptedNames:
+ kind: ""
+ plural: ""
+ conditions: []
+ storedVersions: []
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore b/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore
new file mode 100644
index 00000000..f0c13194
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/.helmignore
@@ -0,0 +1,21 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml
new file mode 100644
index 00000000..dfbe15f9
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/Chart.yaml
@@ -0,0 +1,6 @@
+apiVersion: v1
+description: Kyma component 'compass-runtime-agent'
+name: compass-runtime-agent
+version: 1.0.0
+home: https://kyma-project.io
+icon: https://github.com/kyma-project/kyma/blob/main/logo.png?raw=true
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl
new file mode 100644
index 00000000..a5e2cedf
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/_helpers.tpl
@@ -0,0 +1,18 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
+{{- end -}}
+
+{{/*
+Create a URL for container images
+*/}}
+{{- define "imageurl" -}}
+{{- $registry := default $.reg.path $.img.containerRegistryPath -}}
+{{- $path := ternary (print $registry) (print $registry "/" $.img.directory) (empty $.img.directory) -}}
+{{- $version := ternary (print ":" $.img.version) (print "@sha256:" $.img.sha) (empty $.img.sha) -}}
+{{- print $path "/" $.img.name $version -}}
+{{- end -}}
+
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml
new file mode 100644
index 00000000..c5f50e41
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/cluster-role-binding.yaml
@@ -0,0 +1,48 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ .Chart.Name }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Release.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+rules:
+ - apiGroups: ["compass.kyma-project.io"]
+ resources: ["compassconnections"]
+ verbs: ["create", "get", "list", "update", "delete", "watch"]
+ - apiGroups: ["applicationconnector.kyma-project.io"]
+ resources: ["applications"]
+ verbs: ["get", "list", "create", "update", "delete"]
+ - apiGroups: [""]
+ resources: ["nodes", "persistentvolumes"]
+ verbs: ["get", "list"]
+ - apiGroups: ["metrics.k8s.io"]
+ resources: ["nodes"]
+ verbs: ["get", "list"]
+ - apiGroups: [""]
+ resources: ["secrets"]
+ resourceNames: ["compass-agent-configuration","cluster-client-certificates"]
+ verbs: ["get", "delete"]
+---
+kind: ClusterRoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: {{ .Chart.Name }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ kind: ClusterRole
+ name: {{ .Chart.Name }}
+ apiGroup: rbac.authorization.k8s.io
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml
new file mode 100644
index 00000000..d71d76de
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/deployment.yaml
@@ -0,0 +1,115 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Release.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+spec:
+ selector:
+ matchLabels:
+ app: {{ .Chart.Name }}
+ release: {{ .Release.Name }}
+ template:
+ metadata:
+ annotations:
+ sidecar.istio.io/inject: "true"
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Release.Name }}
+ spec:
+ securityContext:
+ runAsUser: 65535
+ runAsGroup: 65535
+ runAsNonRoot: true
+ seccompProfile:
+ type: RuntimeDefault
+ serviceAccountName: {{ .Chart.Name }}
+ containers:
+ - name: {{ .Chart.Name }}
+ securityContext:
+ privileged: false
+ allowPrivilegeEscalation: false
+ runAsNonRoot: true
+ capabilities:
+ drop:
+ - ALL
+ procMount: default
+ readOnlyRootFilesystem: true
+ ports:
+ - containerPort: {{ .Values.compassRuntimeAgent.healthCheck.port }}
+ hostPort: 0
+ name: http-health
+ image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.compass_runtime_agent) }}
+ imagePullPolicy: {{ .Values.compassRuntimeAgent.image.pullPolicy }}
+ args:
+ - "/app/compass-runtime-agent"
+ env:
+ - name: APP_AGENT_CONFIGURATION_SECRET
+ value: "{{ .Values.compassRuntimeAgent.config.secret.namespace }}/{{ .Values.compassRuntimeAgent.config.secret.name }}"
+ - name: APP_CONTROLLER_SYNC_PERIOD
+ value: {{ .Values.compassRuntimeAgent.sync.controllerSyncPeriod | quote }}
+ - name: APP_MINIMAL_COMPASS_SYNC_TIME
+ value: {{ .Values.compassRuntimeAgent.sync.minimalConfigSyncTime | quote }}
+ - name: APP_CERT_VALIDITY_RENEWAL_THRESHOLD
+ value: {{ .Values.compassRuntimeAgent.certificates.renewal.validityThreshold | quote }}
+ - name: APP_CLUSTER_CERTIFICATES_SECRET
+ value: "{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.name }}"
+ - name: APP_CA_CERTIFICATES_SECRET
+ value: "{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.name }}"
+ - name: APP_SKIP_COMPASS_TLS_VERIFY
+ value: {{ .Values.compassRuntimeAgent.compass.skipTLSVerification | quote }}
+ - name: APP_SKIP_APPS_TLS_VERIFY
+ value: {{ .Values.compassRuntimeAgent.config.skipAppsTLSVerification | quote }}
+ - name: APP_GATEWAY_PORT
+ value: {{ .Values.compassRuntimeAgent.resources.gatewayPort | quote }}
+ - name: APP_UPLOAD_SERVICE_URL
+ value: {{ .Values.compassRuntimeAgent.resources.uploadServiceUrl | quote }}
+ - name: APP_QUERY_LOGGING
+ value: {{ .Values.compassRuntimeAgent.debug.queryLogging | quote }}
+ - name: APP_METRICS_LOGGING_TIME_INTERVAL
+ value: {{ .Values.compassRuntimeAgent.metrics.loggingTimeInterval | quote }}
+ - name: APP_RUNTIME_EVENTS_URL
+ value: "https://gateway.{{ .Values.global.domainName }}"
+ - name: APP_RUNTIME_CONSOLE_URL
+ value: "https://console.{{ .Values.global.domainName }}"
+ - name: APP_HEALTH_PORT
+ value: {{ .Values.compassRuntimeAgent.healthCheck.port | quote }}
+ {{ if .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration}}
+ - name: APP_CA_CERT_SECRET_TO_MIGRATE
+ value: "{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace }}/{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration.name | default "" }}"
+ - name: APP_CA_CERT_SECRET_KEYS_TO_MIGRATE
+ value: '{{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.migration.keys | default "[]" | toJson }}'
+ {{ end }}
+ {{ if .Values.compassRuntimeAgent.config.secret.migration.enabled }}
+ - name: APP_AGENT_CONFIGURATION_SECRET_TO_MIGRATE
+ value: "{{ .Values.compassRuntimeAgent.config.secret.migration.namespace }}/{{ .Values.compassRuntimeAgent.config.secret.name | default "" }}"
+ {{ end }}
+ {{ if .Values.compassRuntimeAgent.certificates.clientCertificate.secret.migration.enabled }}
+ - name: APP_CLUSTER_CERTIFICATES_SECRET_TO_MIGRATE
+ value: "{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.migration.namespace }}/{{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.name | default "" }}"
+ {{ end }}
+ - name: APP_CENTRAL_GATEWAY_SERVICE_URL
+ value: {{ .Values.compassRuntimeAgent.resources.centralGatewayServiceUrl | quote }}
+ livenessProbe:
+ httpGet:
+ port: {{ .Values.compassRuntimeAgent.healthCheck.port }}
+ path: "/healthz"
+ initialDelaySeconds: {{ .Values.compassRuntimeAgent.livenessProbe.initialDelaySeconds }}
+ timeoutSeconds: {{ .Values.compassRuntimeAgent.livenessProbe.timeoutSeconds }}
+ periodSeconds: {{.Values.compassRuntimeAgent.livenessProbe.periodSeconds }}
+ readinessProbe:
+ httpGet:
+ port: {{.Values.compassRuntimeAgent.healthCheck.port }}
+ path: "/healthz"
+ initialDelaySeconds: {{ .Values.compassRuntimeAgent.readinessProbe.initialDelaySeconds }}
+ timeoutSeconds: {{ .Values.compassRuntimeAgent.readinessProbe.timeoutSeconds }}
+ periodSeconds: {{.Values.compassRuntimeAgent.readinessProbe.periodSeconds }}
+ {{- if .Values.priorityClassName }}
+ priorityClassName: {{ .Values.priorityClassName }}
+ {{- end }}
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml
new file mode 100644
index 00000000..037e0819
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/priority-class.yaml
@@ -0,0 +1,7 @@
+apiVersion: scheduling.k8s.io/v1
+kind: PriorityClass
+metadata:
+ name: {{ .Values.priorityClassName }}
+value: 2000000
+globalDefault: false
+description: "Scheduling priority of compass-runtime-agent component. By default, compass-runtime-agent should not be blocked by unschedulable user workloads."
\ No newline at end of file
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml
new file mode 100644
index 00000000..b6c5c3f3
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/resources-role-binding.yaml
@@ -0,0 +1,40 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: {{ .Chart.Name }}
+ namespace: {{ .Values.compassRuntimeAgent.resources.systemNamespace }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+rules:
+ - apiGroups: [""]
+ resources: ["services"]
+ verbs: ["create", "get", "delete"]
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["create", "get", "update", "delete"]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: {{ .Chart.Name }}
+ namespace: {{ .Values.compassRuntimeAgent.resources.systemNamespace }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ kind: Role
+ name: {{ .Chart.Name }}
+ apiGroup: rbac.authorization.k8s.io
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml
new file mode 100644
index 00000000..0c571918
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/role-binding.yaml
@@ -0,0 +1,76 @@
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: {{ .Chart.Name }}-client-cert-role
+ namespace: {{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace | default "default" }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["create", "get", "update", "delete"]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: {{ .Chart.Name }}-client-cert-rolebinding
+ namespace: {{ .Values.compassRuntimeAgent.certificates.clientCertificate.secret.namespace | default "default" }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Release.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ kind: Role
+ name: {{ .Chart.Name }}-client-cert-role
+ apiGroup: rbac.authorization.k8s.io
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: Role
+metadata:
+ name: {{ .Chart.Name }}-ca-cert-role
+ namespace: {{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace | default "default" }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+rules:
+ - apiGroups: [""]
+ resources: ["secrets"]
+ verbs: ["create", "get", "update", "delete"]
+---
+kind: RoleBinding
+apiVersion: rbac.authorization.k8s.io/v1
+metadata:
+ name: {{ .Chart.Name }}-ca-cert-rolebinding
+ namespace: {{ .Values.compassRuntimeAgent.certificates.caCertificate.secret.namespace | default "default" }}
+ labels:
+ app: {{ .Chart.Name }}
+ release: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+roleRef:
+ kind: Role
+ name: {{ .Chart.Name }}-ca-cert-role
+ apiGroup: rbac.authorization.k8s.io
+
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml
new file mode 100644
index 00000000..3a930dc8
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service-account.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ .Chart.Name }}
+ namespace: {{ .Release.Namespace }}
+ labels:
+ app: {{ .Chart.Name }}
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
\ No newline at end of file
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml
new file mode 100644
index 00000000..119457d8
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/service.yaml
@@ -0,0 +1,19 @@
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Chart.Name }}-service
+ namespace: {{ .Values.global.namespace }}
+ labels:
+ control-plane: {{ .Chart.Name }}
+ controller-tools.k8s.io: "1.0"
+ helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
+ app.kubernetes.io/name: {{ template "name" . }}
+ app.kubernetes.io/managed-by: {{ .Release.Service }}
+ app.kubernetes.io/instance: {{ .Release.Name }}
+spec:
+ selector:
+ control-plane: {{ .Chart.Name }}
+ controller-tools.k8s.io: "1.0"
+ ports:
+ - port: 443
+
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml
new file mode 100644
index 00000000..387d761b
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/templates/skr-configmap.yaml
@@ -0,0 +1,7 @@
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: skr-configmap
+ namespace: {{ .Values.global.skrConfigmapNamespace }}
+data:
+ is-managed-kyma-runtime: "true"
diff --git a/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml b/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml
new file mode 100644
index 00000000..dbad8ab5
--- /dev/null
+++ b/tests/hack/ci/resources/charts/compass-runtime-agent/values.yaml
@@ -0,0 +1,89 @@
+global:
+ domainName: kyma.example.com
+ containerRegistry:
+ path: europe-docker.pkg.dev/kyma-project
+ images:
+ compass_runtime_agent:
+ name: "compass-runtime-agent"
+ version: "v20231218-ff3777c4"
+ directory: "prod"
+ istio:
+ gateway:
+ name: kyma-gateway
+ namespace: kyma-system
+ skrConfigmapNamespace: kyma-system
+
+managementPlane: {} # default value
+
+priorityClassName: "compass-runtime-agent-priority-class"
+
+compassRuntimeAgent:
+ image:
+ pullPolicy: IfNotPresent
+ sync:
+ controllerSyncPeriod: 180s
+ minimalConfigSyncTime: 15s
+ resources:
+ systemNamespace: "kyma-system"
+ dexSecretNamespace: "kyma-system"
+ dexSecretName: "admin-user"
+ gatewayPort: 8080
+ centralGatewayServiceUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082
+ config:
+ insecureConfigurationFetch: true
+ skipAppsTLSVerification: false
+ secret:
+ name: compass-agent-configuration
+ namespace: kyma-system
+ migration:
+ namespace: compass-system
+ enabled: true
+ certificates:
+ renewal:
+ validityThreshold: "0.3"
+ clientCertificate:
+ secret:
+ name: cluster-client-certificates
+ namespace: kyma-system
+ migration:
+ namespace: compass-system
+ enabled: true
+ caCertificate:
+ secret:
+ name: kyma-gateway-certs-cacert
+ namespace: istio-system
+ migration:
+ name: app-connector-certs
+ keys: ["cacert"]
+ compass:
+ skipTLSVerification: true
+ debug:
+ queryLogging: false
+ metrics:
+ loggingTimeInterval: 30m
+ healthCheck:
+ port: 8090
+ proxyStatusPort: 15020
+ tests:
+ labels:
+ integration: true
+ after-upgrade: true
+ enabled: true
+ mockService:
+ port: 8080
+ configApplicationWaitTime: 50s
+ proxyInvalidationWaitTime: 120s
+ applicationInstallationTimeout: 180s
+ graphqlLogs: false
+ director:
+ url: "https://compass-gateway.{{ .Values.global.domainName }}/director/graphql"
+ idProvider:
+ clientTimeout: 10s
+ livenessProbe:
+ initialDelaySeconds: 50
+ timeoutSeconds: 1
+ periodSeconds: 10
+ readinessProbe:
+ initialDelaySeconds: 10
+ timeoutSeconds: 1
+ periodSeconds: 2
diff --git a/tests/internal/testkit/httpd/http.go b/tests/internal/testkit/httpd/http.go
new file mode 100644
index 00000000..694b03e2
--- /dev/null
+++ b/tests/internal/testkit/httpd/http.go
@@ -0,0 +1,44 @@
+package httpd
+
+import (
+ "io"
+ "net/http"
+ "testing"
+)
+
+type LogHttp struct {
+ t *testing.T
+ httpCli *http.Client
+}
+
+func NewCli(t *testing.T) LogHttp {
+ return LogHttp{t: t, httpCli: &http.Client{}}
+}
+
+func (c LogHttp) Get(url string) (resp *http.Response, body []byte, err error) {
+ c.t.Helper()
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ if err != nil {
+ return
+ }
+
+ return c.Do(req)
+
+}
+
+func (c LogHttp) Do(req *http.Request) (res *http.Response, body []byte, err error) {
+ c.t.Helper()
+ c.t.Logf("%s %s", req.Method, req.URL)
+
+ res, err = c.httpCli.Do(req)
+ if err != nil {
+ return
+ }
+
+ body, err = io.ReadAll(res.Body)
+ if err == nil && len(body) > 0 {
+ c.t.Logf("Body: %s", body)
+ }
+
+ return
+}
diff --git a/tests/internal/testkit/test-api/apis.go b/tests/internal/testkit/test-api/apis.go
new file mode 100644
index 00000000..98c04105
--- /dev/null
+++ b/tests/internal/testkit/test-api/apis.go
@@ -0,0 +1,122 @@
+package test_api
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/go-http-utils/logger"
+ "github.com/gorilla/mux"
+ log "github.com/sirupsen/logrus"
+)
+
+func SetupRoutes(logOut io.Writer, basicAuthCredentials BasicAuthCredentials, oAuthCredentials OAuthCredentials, expectedRequestParameters ExpectedRequestParameters, oauthTokens map[string]OAuthToken, csrfTokens CSRFTokens) http.Handler {
+ router := mux.NewRouter()
+
+ router.HandleFunc("/v1/health", alwaysOk).Methods("GET")
+ api := router.PathPrefix("/v1/api").Subrouter()
+ api.Use(Logger(logOut, logger.DevLoggerType))
+
+ oauth := NewOAuth(oAuthCredentials.ClientID, oAuthCredentials.ClientSecret, oauthTokens)
+ csrf := NewCSRF(csrfTokens)
+
+ {
+ api.HandleFunc("/oauth/token", oauth.Token).Methods(http.MethodPost)
+ api.HandleFunc("/oauth/bad-token", oauth.BadToken).Methods(http.MethodPost)
+ api.HandleFunc("/csrf/token", csrf.Token).Methods(http.MethodGet)
+ api.HandleFunc("/csrf/bad-token", csrf.BadToken).Methods(http.MethodGet)
+ }
+
+ {
+ r := api.PathPrefix("/unsecure").Subrouter()
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ r.HandleFunc("/echo", echo).Methods(http.MethodPut, http.MethodPost, http.MethodDelete)
+ r.HandleFunc("/code/{code:[0-9]+}", resCode).Methods(http.MethodGet)
+ r.HandleFunc("/timeout", timeout).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/basic").Subrouter()
+ r.Use(BasicAuth(basicAuthCredentials))
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/oauth").Subrouter()
+ r.Use(oauth.Middleware())
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/csrf-basic").Subrouter()
+ r.Use(csrf.Middleware())
+ r.Use(BasicAuth(basicAuthCredentials))
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/csrf-oauth").Subrouter()
+ r.Use(csrf.Middleware())
+ r.Use(oauth.Middleware())
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/request-parameters-basic").Subrouter()
+ r.Use(RequestParameters(expectedRequestParameters))
+ r.Use(BasicAuth(basicAuthCredentials))
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/redirect").Subrouter()
+
+ r.HandleFunc("/ok/target", alwaysOk).Methods(http.MethodGet)
+
+ r.Handle("/ok", http.RedirectHandler("/v1/api/redirect/ok/target", http.StatusTemporaryRedirect))
+
+ ba := BasicAuth(basicAuthCredentials)
+ ok := http.HandlerFunc(alwaysOk)
+ r.Handle("/basic/target", ba(ok)).Methods(http.MethodGet)
+ r.Handle("/basic", http.RedirectHandler("/v1/api/redirect/basic/target", http.StatusTemporaryRedirect))
+
+ r.Handle("/external", http.RedirectHandler("http://central-application-gateway.kyma-system:8081/v1/health", http.StatusTemporaryRedirect))
+ }
+
+ return router
+}
+
+func SetupMTLSRoutes(logOut io.Writer, oAuthCredentials OAuthCredentials, oauthTokens map[string]OAuthToken, csrfTokens CSRFTokens) http.Handler {
+ router := mux.NewRouter()
+
+ router.HandleFunc("/v1/health", alwaysOk).Methods("GET")
+ api := router.PathPrefix("/v1/api").Subrouter()
+ api.Use(Logger(logOut, logger.DevLoggerType))
+
+ oauth := NewOAuth(oAuthCredentials.ClientID, oAuthCredentials.ClientSecret, oauthTokens)
+ csrf := NewCSRF(csrfTokens)
+
+ {
+ r := api.PathPrefix("/mtls").Subrouter()
+ r.Use(oauth.Middleware())
+ api.HandleFunc("/mtls-oauth/token", oauth.MTLSToken).Methods(http.MethodPost)
+ }
+
+ {
+ r := api.PathPrefix("/mtls").Subrouter()
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+ {
+ r := api.PathPrefix("/csrf-mtls").Subrouter()
+ r.Use(csrf.Middleware())
+ r.HandleFunc("/ok", alwaysOk).Methods(http.MethodGet)
+ }
+
+ return router
+}
+
+func Logger(out io.Writer, t logger.Type) mux.MiddlewareFunc {
+ return func(next http.Handler) http.Handler {
+ return logger.Handler(next, out, t)
+ }
+}
+
+func handleError(w http.ResponseWriter, code int, format string, a ...interface{}) {
+ err := fmt.Errorf(format, a...)
+ log.Error(err)
+ w.WriteHeader(code)
+}
diff --git a/tests/internal/testkit/test-api/basicauth.go b/tests/internal/testkit/test-api/basicauth.go
new file mode 100644
index 00000000..0307ae94
--- /dev/null
+++ b/tests/internal/testkit/test-api/basicauth.go
@@ -0,0 +1,30 @@
+package test_api
+
+import (
+ "github.com/gorilla/mux"
+ "net/http"
+)
+
+type BasicAuthCredentials struct {
+ User string
+ Password string
+}
+
+func BasicAuth(credentials BasicAuthCredentials) mux.MiddlewareFunc {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ u, p, ok := r.BasicAuth()
+ if !ok {
+ handleError(w, http.StatusForbidden, "Basic auth header not found")
+ return
+ }
+
+ if credentials.User != u || credentials.Password != p {
+ handleError(w, http.StatusForbidden, "Incorrect username or Password")
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
diff --git a/tests/internal/testkit/test-api/csrf.go b/tests/internal/testkit/test-api/csrf.go
new file mode 100644
index 00000000..06f7993b
--- /dev/null
+++ b/tests/internal/testkit/test-api/csrf.go
@@ -0,0 +1,95 @@
+package test_api
+
+import (
+ "github.com/google/uuid"
+ "github.com/gorilla/mux"
+ "net/http"
+ "sync"
+)
+
+const (
+ csrfTokenHeader = "X-csrf-token"
+ csrfTokenCookie = "csrftokencookie"
+)
+
+type CSRFTokens map[string]interface{}
+
+type CSRFHandler struct {
+ mutex sync.RWMutex
+ tokens map[string]interface{}
+}
+
+func NewCSRF(tokens CSRFTokens) CSRFHandler {
+ return CSRFHandler{
+ mutex: sync.RWMutex{},
+ tokens: tokens,
+ }
+}
+
+func (ch *CSRFHandler) Token(w http.ResponseWriter, _ *http.Request) {
+ token := uuid.New().String()
+
+ ch.mutex.Lock()
+ ch.tokens[token] = nil
+ ch.mutex.Unlock()
+
+ w.Header().Set(csrfTokenHeader, token)
+ http.SetCookie(w, &http.Cookie{
+ Name: csrfTokenCookie,
+ Value: token,
+ })
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+}
+
+func (ch *CSRFHandler) BadToken(w http.ResponseWriter, _ *http.Request) {
+ token := uuid.New().String()
+
+ w.Header().Set(csrfTokenHeader, token)
+ http.SetCookie(w, &http.Cookie{
+ Name: csrfTokenCookie,
+ Value: token,
+ })
+
+ w.WriteHeader(http.StatusOK)
+ w.Header().Set("Content-Type", "application/json")
+}
+
+func (ch *CSRFHandler) Middleware() mux.MiddlewareFunc {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ headerToken := r.Header.Get(csrfTokenHeader)
+ if headerToken == "" {
+ handleError(w, http.StatusForbidden, "CSRF token header missing")
+ return
+ }
+
+ ch.mutex.RLock()
+ _, found := ch.tokens[headerToken]
+ ch.mutex.RUnlock()
+
+ if !found {
+ handleError(w, http.StatusForbidden, "Invalid CSRF token from the header")
+ return
+ }
+
+ cookieToken, err := r.Cookie(csrfTokenCookie)
+ if err != nil {
+ handleError(w, http.StatusForbidden, "CSRF token cookie missing")
+ return
+ }
+
+ ch.mutex.RLock()
+ _, found = ch.tokens[cookieToken.Value]
+ ch.mutex.RUnlock()
+
+ if !found {
+ handleError(w, http.StatusForbidden, "Invalid CSRF token from the cookie")
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
diff --git a/tests/internal/testkit/test-api/handlers.go b/tests/internal/testkit/test-api/handlers.go
new file mode 100644
index 00000000..26114863
--- /dev/null
+++ b/tests/internal/testkit/test-api/handlers.go
@@ -0,0 +1,69 @@
+package test_api
+
+import (
+ "encoding/json"
+ "io/ioutil"
+ "log"
+ "net/http"
+ "strconv"
+ "time"
+
+ "github.com/gorilla/mux"
+)
+
+func alwaysOk(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusOK)
+}
+
+type EchoResponse struct {
+ Body []byte `json:"body"`
+ Headers map[string][]string `json:"headers"`
+ Method string `json:"method"`
+ Query string `json:"query"`
+}
+
+func echo(w http.ResponseWriter, r *http.Request) {
+ body, err := ioutil.ReadAll(r.Body)
+ if err != nil {
+ log.Println("Couldn't read request body:", r.URL)
+ body = []byte("")
+ }
+
+ res := EchoResponse{
+ Method: r.Method,
+ Body: body,
+ Headers: r.Header,
+ Query: r.URL.RawQuery,
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ err = json.NewEncoder(w).Encode(res)
+
+ if err != nil {
+ log.Println("Couldn't encode the response body to JSON:", r.URL)
+ }
+}
+
+// resCode should only be used in paths with `code`
+// parameter, that is a valid int
+func resCode(w http.ResponseWriter, r *http.Request) {
+ vars := mux.Vars(r)
+ codeStr := vars["code"] // must exist, because path has a pattern
+ code, _ := strconv.Atoi(codeStr) // can't error, because path has a pattern
+ w.WriteHeader(code)
+ w.Write([]byte(codeStr))
+}
+
+func timeout(w http.ResponseWriter, r *http.Request) {
+ c := r.Context().Done()
+ if c == nil {
+ log.Println("Context has no timeout, sleeping for 2 minutes")
+ time.Sleep(2 * time.Minute)
+ return
+ }
+ log.Println("Context timeout, waiting until done")
+
+ _ = <-c
+
+ alwaysOk(w, r)
+}
diff --git a/tests/internal/testkit/test-api/oauth.go b/tests/internal/testkit/test-api/oauth.go
new file mode 100644
index 00000000..2fe60ea3
--- /dev/null
+++ b/tests/internal/testkit/test-api/oauth.go
@@ -0,0 +1,190 @@
+package test_api
+
+import (
+ "encoding/json"
+ "fmt"
+ "github.com/gorilla/mux"
+ "net/http"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/google/uuid"
+ log "github.com/sirupsen/logrus"
+)
+
+type OAuthCredentials struct {
+ ClientID string
+ ClientSecret string
+}
+
+const (
+ clientIDKey = "client_id"
+ clientSecretKey = "client_secret"
+ grantTypeKey = "grant_type"
+ tokenLifetime = "token_lifetime"
+ defaultTokenExpiresIn = 5 * time.Minute
+)
+
+type OauthResponse struct {
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int64 `json:"expires_in,omitempty"`
+}
+
+type OAuthToken struct {
+ exp time.Time
+}
+
+func (token OAuthToken) Valid() bool {
+ return token.exp.After(time.Now())
+}
+
+type OAuthHandler struct {
+ clientID string
+ clientSecret string
+ mutex sync.RWMutex
+ tokens map[string]OAuthToken
+}
+
+func NewOAuth(clientID, clientSecret string, tokens map[string]OAuthToken) OAuthHandler {
+ return OAuthHandler{
+ clientID: clientID,
+ clientSecret: clientSecret,
+ mutex: sync.RWMutex{},
+ tokens: tokens,
+ }
+}
+
+func (oh *OAuthHandler) Token(w http.ResponseWriter, r *http.Request) {
+ if ok, status, message := oh.isRequestValid(r); !ok {
+ handleError(w, status, message)
+ return
+ }
+
+ token := uuid.New().String()
+ exp := defaultTokenExpiresIn
+
+ if ttlStr := r.URL.Query().Get(tokenLifetime); ttlStr != "" {
+ parsedEXP, err := time.ParseDuration(ttlStr)
+ if err == nil {
+ log.Info("Received valid OAuth expiresIn:", parsedEXP)
+ exp = parsedEXP
+ } else {
+ log.Error("Received invalid OAuth expiresIn:", err)
+ }
+ }
+
+ oh.storeTokenInCache(token, exp)
+
+ response := OauthResponse{AccessToken: token, TokenType: "bearer", ExpiresIn: int64(exp.Seconds())}
+ oh.respondWithToken(w, response)
+}
+
+func (oh *OAuthHandler) BadToken(w http.ResponseWriter, r *http.Request) {
+ if ok, status, message := oh.isRequestValid(r); !ok {
+ handleError(w, status, message)
+ return
+ }
+
+ token := uuid.New().String()
+ response := OauthResponse{AccessToken: token, TokenType: "bearer"}
+ oh.respondWithToken(w, response)
+}
+
+func (oh *OAuthHandler) MTLSToken(w http.ResponseWriter, r *http.Request) {
+ if ok, status, message := oh.isMTLSRequestValid(r); !ok {
+ handleError(w, status, message)
+ return
+ }
+
+ token := uuid.New().String()
+ exp := defaultTokenExpiresIn
+
+ oh.storeTokenInCache(token, exp)
+ response := OauthResponse{AccessToken: token, TokenType: "bearer", ExpiresIn: 3600}
+ oh.respondWithToken(w, response)
+}
+
+func (oh *OAuthHandler) Middleware() mux.MiddlewareFunc {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ authHeader := r.Header.Get("Authorization")
+ if authHeader == "" {
+ handleError(w, http.StatusUnauthorized, "Authorization header missing")
+ return
+ }
+
+ splitToken := strings.Split(authHeader, "Bearer")
+ if len(splitToken) != 2 {
+ handleError(w, http.StatusUnauthorized, "Bearer token missing")
+ return
+ }
+
+ token := strings.TrimSpace(splitToken[1])
+
+ oh.mutex.RLock()
+ data, found := oh.tokens[token]
+ oh.mutex.RUnlock()
+
+ if !found || !data.Valid() {
+ handleError(w, http.StatusUnauthorized, "Invalid token")
+ return
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
+func (oh *OAuthHandler) isRequestValid(r *http.Request) (bool, int, string) {
+ err := r.ParseForm()
+ if err != nil {
+ return false, http.StatusInternalServerError, fmt.Sprintf("Failed to parse form: %v", err)
+ }
+
+ clientID := r.FormValue(clientIDKey)
+ clientSecret := r.FormValue(clientSecretKey)
+ grantType := r.FormValue(grantTypeKey)
+
+ if !oh.verifyClient(clientID, clientSecret) || grantType != "client_credentials" {
+ return false, http.StatusForbidden, "Client verification failed"
+ }
+
+ return true, 0, ""
+}
+
+func (oh *OAuthHandler) verifyClient(id, secret string) bool {
+ return id == oh.clientID && secret == oh.clientSecret
+}
+
+func (oh *OAuthHandler) respondWithToken(w http.ResponseWriter, response OauthResponse) {
+ w.Header().Set("Content-Type", "application/json")
+ if err := json.NewEncoder(w).Encode(response); err != nil {
+ handleError(w, http.StatusInternalServerError, "Failed to encode token response")
+ return
+ }
+ w.WriteHeader(http.StatusOK)
+}
+
+func (oh *OAuthHandler) storeTokenInCache(token string, expIn time.Duration) {
+ oh.mutex.Lock()
+ oh.tokens[token] = OAuthToken{exp: time.Now().Add(expIn)}
+ oh.mutex.Unlock()
+}
+
+func (oh *OAuthHandler) isMTLSRequestValid(r *http.Request) (bool, int, string) {
+ err := r.ParseForm()
+ if err != nil {
+ return false, http.StatusInternalServerError, fmt.Sprintf("Failed to parse form: %v", err)
+ }
+
+ clientID := r.FormValue(clientIDKey)
+ grantType := r.FormValue(grantTypeKey)
+
+ if r.TLS == nil || clientID != oh.clientID || grantType != "client_credentials" {
+ return false, http.StatusForbidden, "Client verification failed"
+ }
+
+ return true, 0, ""
+}
diff --git a/tests/internal/testkit/test-api/requestparams.go b/tests/internal/testkit/test-api/requestparams.go
new file mode 100644
index 00000000..774ff9fa
--- /dev/null
+++ b/tests/internal/testkit/test-api/requestparams.go
@@ -0,0 +1,53 @@
+package test_api
+
+import (
+ "github.com/gorilla/mux"
+ "net/http"
+)
+
+type ExpectedRequestParameters struct {
+ Headers map[string][]string
+ QueryParameters map[string][]string
+}
+
+func RequestParameters(expectedRequestParams ExpectedRequestParameters) mux.MiddlewareFunc {
+ return func(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ for key, expectedVals := range expectedRequestParams.Headers {
+ actualVals := r.Header.Values(key)
+ if !containsSubset(actualVals, expectedVals) {
+ handleError(w, http.StatusBadRequest, "Incorrect additional headers. Expected %s header to contain %v, but found %v", key, expectedVals, actualVals)
+ return
+ }
+ }
+
+ queryParameters := r.URL.Query()
+ for key, expectedVals := range expectedRequestParams.QueryParameters {
+ actualVals := queryParameters[key]
+ if !containsSubset(actualVals, expectedVals) {
+ handleError(w, http.StatusBadRequest, "Incorrect additional query parameters. Expected %s query parameter to contain %v, but found %v", key, expectedVals, actualVals)
+ return
+ }
+ }
+
+ next.ServeHTTP(w, r)
+ })
+ }
+}
+
+func containsSubset(set, subset []string) bool {
+ for _, bVal := range subset {
+ found := false
+ for _, aVal := range set {
+ if aVal == bVal {
+ found = true
+ break
+ }
+ }
+
+ if !found {
+ return false
+ }
+ }
+ return true
+}
diff --git a/tests/resources/charts/application-connectivity-validator-test/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/Chart.yaml
new file mode 100644
index 00000000..0424adb3
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/Chart.yaml
@@ -0,0 +1,23 @@
+apiVersion: v2
+name: application-connectivity-validator-test
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 1.16.0
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml
new file mode 100644
index 00000000..807b2174
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/Chart.yaml
@@ -0,0 +1,23 @@
+apiVersion: v2
+name: echoserver
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 1.16.0
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml
new file mode 100644
index 00000000..d4f83b72
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/echoserver/templates/echoserver.yml
@@ -0,0 +1,41 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: echosever
+ name: echoserver
+ namespace: {{ .Values.global.namespace }}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: echoserver
+ template:
+ metadata:
+ labels:
+ app: echoserver
+ spec:
+ containers:
+ - image: ealen/echo-server:0.7.0
+ name: echoserver
+ ports:
+ - containerPort: 80
+ livenessProbe:
+ httpGet:
+ path: /
+ port: 80
+ initialDelaySeconds: 3
+ periodSeconds: 3
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: echoserver
+ namespace: {{ .Values.global.namespace }}
+spec:
+ selector:
+ app: echoserver
+ ports:
+ - name: "http"
+ protocol: TCP
+ port: 80
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml b/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml
new file mode 100644
index 00000000..6e99aa1f
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/Chart.yaml
@@ -0,0 +1,23 @@
+apiVersion: v2
+name: test
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 1.16.0
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl
new file mode 100644
index 00000000..5acdb5e3
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/_helpers.tpl
@@ -0,0 +1,11 @@
+{{/*
+Create a URL for container images
+*/}}
+{{- define "imageurl" -}}
+{{- $registry := default $.reg.path $.img.containerRegistryPath -}}
+{{- if hasKey $.img "directory" -}}
+{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}}
+{{- else -}}
+{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}}
+{{- end -}}
+{{- end -}}
\ No newline at end of file
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml
new file mode 100644
index 00000000..8e1a498e
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-compass.yml
@@ -0,0 +1,12 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: event-test-compass
+spec:
+ compassMetadata:
+ applicationId: applicationId
+ authentication:
+ clientIds: ["clientId1", "clientId2"]
+ description: Test app-con-validator
+ skipVerify: true
+
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml
new file mode 100644
index 00000000..424b7bd6
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/applications/event-test-standalone.yml
@@ -0,0 +1,7 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: event-test-standalone
+spec:
+ description: Test app-con-validator
+ skipVerify: true
diff --git a/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml
new file mode 100644
index 00000000..12eb1f67
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/charts/test/templates/test.yml
@@ -0,0 +1,17 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: application-connectivity-validator-test
+ namespace: {{ .Values.global.namespace }}
+spec:
+ backoffLimit: 0
+ template:
+ metadata:
+ annotations:
+ traffic.sidecar.istio.io/excludeOutboundPorts: "8080"
+ spec:
+ containers:
+ - name: application-connectivity-validator-test
+ image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.validatorTest) }}
+ imagePullPolicy: Always
+ restartPolicy: Never
\ No newline at end of file
diff --git a/tests/resources/charts/application-connectivity-validator-test/values.yaml b/tests/resources/charts/application-connectivity-validator-test/values.yaml
new file mode 100644
index 00000000..5b84bcba
--- /dev/null
+++ b/tests/resources/charts/application-connectivity-validator-test/values.yaml
@@ -0,0 +1,15 @@
+# Default values for application-connectivity-validator-test.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+global:
+ containerRegistry:
+ path: "europe-docker.pkg.dev/kyma-project"
+
+ images:
+ validatorTest:
+ name: "connectivity-validator-test"
+ version: "v20230925-75c3a9a8"
+ directory: "prod"
+
+ namespace: "test"
diff --git a/tests/resources/charts/compass-runtime-agent-test/Chart.yaml b/tests/resources/charts/compass-runtime-agent-test/Chart.yaml
new file mode 100644
index 00000000..9b3c49e9
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: compass-runtime-agent-test
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.1.0"
diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl b/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl
new file mode 100644
index 00000000..9cba1391
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/templates/_helpers.tpl
@@ -0,0 +1,12 @@
+
+{{/*
+Create a URL for container images
+*/}}
+{{- define "imageurl" -}}
+{{- $registry := default $.reg.path $.img.containerRegistryPath -}}
+{{- if hasKey $.img "directory" -}}
+{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}}
+{{- else -}}
+{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}}
+{{- end -}}
+{{- end -}}
diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml
new file mode 100644
index 00000000..e95e05cd
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/templates/applications/test-create-app.yaml
@@ -0,0 +1,59 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ labels:
+ applicationconnector.kyma-project.io/managed-by: compass-runtime-agent
+ name: app1
+spec:
+ description: Test Application for testing Compass Runtime Agent
+ displayName: ""
+ longDescription: ""
+ providerDisplayName: ""
+ skipVerify: false
+ services:
+ - description: Foo bar
+ displayName: bndl-app-1
+ entries:
+ - centralGatewayUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082/mp-app1gkhavxduzb/bndl-app-1/comments-v1
+ credentials:
+ secretName: ""
+ type: ""
+ gatewayUrl: ""
+ id: 30747de1-4a87-4b67-a75d-9fe84af6e6f9
+ name: comments-v1
+ targetUrl: http://mywordpress.com/comments
+ type: API
+ id: e4148ee9-79c0-4d81-863c-311f32aeed9b
+ identifier: ""
+ name: bndl-app-1-0d79e
+ providerDisplayName: ""
+---
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ labels:
+ applicationconnector.kyma-project.io/managed-by: compass-runtime-agent
+ name: app1-updated
+spec:
+ description: "The app was updated"
+ displayName: ""
+ longDescription: ""
+ providerDisplayName: ""
+ services:
+ - description: Foo bar
+ displayName: bndl-app-1
+ entries:
+ - centralGatewayUrl: http://central-application-gateway.kyma-system.svc.cluster.local:8082/mp-app1gkhavxduzb/bndl-app-1/comments-v1
+ credentials:
+ secretName: ""
+ type: ""
+ gatewayUrl: ""
+ id: 30747de1-4a87-4b67-a75d-9fe84af6e6f9
+ name: comments-v1
+ targetUrl: http://mywordpress.com/comments
+ type: API
+ id: e4148ee9-79c0-4d81-863c-311f32aeed9b
+ identifier: ""
+ name: bndl-app-1-0d79e
+ providerDisplayName: ""
+ skipVerify: false
diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml
new file mode 100644
index 00000000..8a76d2cf
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/templates/secret-compass.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: {{ .Values.oauthCredentialsSecretName }}
+ namespace: {{ .Values.oauthCredentialsNamespace }}
+data:
+ client_id: {{ .Values.compassCredentials.clientID | b64enc | quote }}
+ client_secret: {{ .Values.compassCredentials.clientSecret | b64enc | quote }}
+ tokens_endpoint: {{ .Values.compassCredentials.tokensEndpoint | b64enc | quote }}
+type: Opaque
\ No newline at end of file
diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml
new file mode 100644
index 00000000..92c53e1d
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/templates/service-account.yaml
@@ -0,0 +1,59 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ .Values.serviceAccountName }}
+ namespace: {{ .Values.namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ .Values.serviceAccountName }}
+ namespace: {{ .Values.namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ .Values.serviceAccountName }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Values.serviceAccountName }}
+ namespace: {{ .Values.namespace }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ .Values.serviceAccountName }}
+rules:
+ - apiGroups:
+ - ""
+ resources:
+ - secrets
+ verbs:
+ - create
+ - get
+ - list
+ - delete
+ - apiGroups:
+ - "apps"
+ resources:
+ - deployments
+ verbs:
+ - get
+ - list
+ - update
+ - apiGroups:
+ - "applicationconnector.kyma-project.io"
+ resources:
+ - "applications"
+ verbs:
+ - get
+ - list
+ - apiGroups:
+ - "compass.kyma-project.io"
+ resources:
+ - "compassconnections"
+ verbs:
+ - create
+ - get
+ - delete
+ - update
+ - list
\ No newline at end of file
diff --git a/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml b/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml
new file mode 100644
index 00000000..639a8b8d
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/templates/test.yaml
@@ -0,0 +1,26 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: compass-runtime-agent-test
+ namespace: {{ .Values.namespace }}
+spec:
+ template:
+ spec:
+ restartPolicy: Never
+ serviceAccountName: {{ .Values.serviceAccountName }}
+ containers:
+ - name: compass-runtime-agent-test
+ image: {{ include "imageurl" (dict "reg" .Values.containerRegistry "img" .Values.images.compassTest) }}
+ imagePullPolicy: Always
+ env:
+ - name: APP_DIRECTOR_URL
+ value: {{ .Values.directorUrl }}
+ - name: APP_TESTING_TENANT
+ value: {{ .Values.testTenant }}
+ - name: APP_SKIP_DIRECTOR_CERT_VERIFICATION
+ value: {{ .Values.skipDirectorCertVerification | quote }}
+ - name: APP_OAUTH_CREDENTIALS_SECRET_NAME
+ value: {{.Values.oauthCredentialsSecretName}}
+ - name: APP_OAUTH_CREDENTIALS_NAMESPACE
+ value: {{ .Values.oauthCredentialsNamespace }}
+ backoffLimit: 0
diff --git a/tests/resources/charts/compass-runtime-agent-test/values.yaml b/tests/resources/charts/compass-runtime-agent-test/values.yaml
new file mode 100644
index 00000000..193f3e9e
--- /dev/null
+++ b/tests/resources/charts/compass-runtime-agent-test/values.yaml
@@ -0,0 +1,19 @@
+namespace: "test"
+testTenant: "461f6292-8085-41c8-af0c-e185f39b5e18"
+oauthCredentialsSecretName: "oauth-compass-credentials"
+oauthCredentialsNamespace: "test"
+skipDirectorCertVerification: true
+serviceAccountName: "test-compass-runtime-agent"
+
+containerRegistry:
+ path: "k3d-kyma-registry:5000"
+
+images:
+ compassTest:
+ name: "compass-runtime-agent-test"
+ version: "002"
+
+compassCredentials:
+ clientID: ""
+ clientSecret: ""
+ tokensEndpoint: ""
diff --git a/tests/resources/charts/gateway-test/Chart.yaml b/tests/resources/charts/gateway-test/Chart.yaml
new file mode 100644
index 00000000..ea515d61
--- /dev/null
+++ b/tests/resources/charts/gateway-test/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: application-gateway-test
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.1.0"
diff --git a/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml b/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml
new file mode 100644
index 00000000..5671f8ac
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/mock-app/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: mock-app
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.1.0"
diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl b/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl
new file mode 100644
index 00000000..9cba1391
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/_helpers.tpl
@@ -0,0 +1,12 @@
+
+{{/*
+Create a URL for container images
+*/}}
+{{- define "imageurl" -}}
+{{- $registry := default $.reg.path $.img.containerRegistryPath -}}
+{{- if hasKey $.img "directory" -}}
+{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}}
+{{- else -}}
+{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}}
+{{- end -}}
+{{- end -}}
diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml
new file mode 100644
index 00000000..0a968871
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/expired-mtls-cert-secret.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: expired-mtls-cert-secret
+ namespace: test
+type: Opaque
+data:
+ # Server certificate expired on 02.08.2022
+ server.crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVWnVOZ3FCQTdSUEs4bStSSnhuaXRTS21xZFdZd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTQ4cTdiU21rSVZwQng5SWo3Si9qWGFJNU85dGs5dEJlTlhxcGNQM2lsTUtFMVpqTmhyb0wKRjNNV2xnWU10cCtBRlEvKzhYdjlESFVCMWtaNnU4QjZiQzFjV1NSZWMrUWY1ZThYNXZlZkFodng2ZHhCem0wbgo5MDhodTVhRFFQUXdCOHdsYSsyRHAzTjZVdTJQcW01cXk1U0xPcFZxN3hnaHBiUFBQSndoZGNIZFoxN0hvckl2CmcvdUt2M3JLdWJtY2U1MDdDaGFVWGhsWTUyeUtHelRvdXp5R3RqRjhOelRWSmQ1QmtFWGhmVW5HdWttbHBOSG8KRHNUWEJYcE9jNkdxb1VmL1FueUdQTTNtVEVkVUJEQzVEOGhtaUhSU3UyM3VHa2VLdGpWV2o1Ukdydkx2aUdRcgpoeHRhcStzNzBPTUtVbURaRDBMU3MyNXNwVzFnUWtkUzRRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUMrNnM3TmJlYSs2RnRibSt1V2FUQjZSSTkzMEpIRUhEd3Y5YWQvR2VXZllZd2tBVXpQMApja1VKSzZDWHVCa2FxYzVOSkpuRmVGZGpIOXZ4MHVBSkxvNXdiZUpGTjB6SXRVdTNBMGtEMnd2U3VXTGJCbEhBCnFiTWxQMm9FMWxsU3hZZDhRMFpPaDllUld1MkMzMitjdnorRWw2RXFzbE9OVmlrZWEyNWJvYnFnelJhY2FCM0UKMG5KQUgwOUFyMys3MllzSHFNd2EyUm51bWVORDJWdGNGT3JraWt3ZkxJbml5NFJpQkdSY3RhVTF6bk9oZVpMNQoxZFJDOURhMk5nZzNrb2tDb3ZibmtHcXZsWk80ZnJJeEw1U244WjFXcEl4dXNoTzBaWjdkeWdaT0FtSEluQzd0CmJYSHJsb3VDclh4TGlHKy9qRnVvMXRZYlFHN2VUWE8rWDNBPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+ server.key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcGdJQkFBS0NBUUVBNDhxN2JTbWtJVnBCeDlJajdKL2pYYUk1Tzl0azl0QmVOWHFwY1AzaWxNS0UxWmpOCmhyb0xGM01XbGdZTXRwK0FGUS8rOFh2OURIVUIxa1o2dThCNmJDMWNXU1JlYytRZjVlOFg1dmVmQWh2eDZkeEIKem0wbjkwOGh1NWFEUVBRd0I4d2xhKzJEcDNONlV1MlBxbTVxeTVTTE9wVnE3eGdocGJQUFBKd2hkY0hkWjE3SApvckl2Zy91S3Yzckt1Ym1jZTUwN0NoYVVYaGxZNTJ5S0d6VG91enlHdGpGOE56VFZKZDVCa0VYaGZVbkd1a21sCnBOSG9Ec1RYQlhwT2M2R3FvVWYvUW55R1BNM21URWRVQkRDNUQ4aG1pSFJTdTIzdUdrZUt0alZXajVSR3J2THYKaUdRcmh4dGFxK3M3ME9NS1VtRFpEMExTczI1c3BXMWdRa2RTNFFJREFRQUJBb0lCQVFEZlB1QWpZeTBsTnRURApKakxwQStZTDdTSVVoTGRWb083RGtOeWhEV0ZUazdRbHRpU3ZSb1A2VG1PelVtaUJUcDV6aGdMQTNsZ3BMajlICnBqbEE2cW5RZlVCRmFQeGNyaFdJL3FNNVRETjlHTEFsRnlVelR3MWROaU9FT2tXV2tmckVtWkdQVGU2NlhOVmsKa3NnN0t3M2xTVWFPZXNPYllkWVFGTUlrejR1SFlFZlEwbWprTUVzdWJLWDRVVmJpbzg3Q0ljbHVYTFRodnBLNwpMWGZVdjdIdXJFY0hxdkVPbDhZYXdJckdLL1VMUWJLV3ZPN0xxS2hzTzJiSmhpbWoyNjVBZGwrNjdpbXJabHRHCnExVWM3V2lVOXlkeVNycTgrbVd0elpwbVpWR1N4RFkzcXlDeEJIbmJKNDNJMFZ3SktLcURMUzVMQTkvc0ZyZUwKVUpFdWlxYmxBb0dCQVBhYU4rdmJuWGlrTEFnckhONDRjQ252QkxhUkV4YmFyVVdMT1ZHVStnWUVtenRveUdBdgpkSWdXeE1maExHL2J5Z29SWmpxenc1TTBHMGVBTTJEWUpZV1hJM1NYR3RXM3B1Vzl4b0pMS09WN2c3anorNjZJCkpSaTFqeUY0QVdOZXpZUWdUZG5ncndULys5bHhOSThMbUMxN2wrSWprNXR2Um5WVCtUQTRRSGw3QW9HQkFPeDUKQVFUZ1QxWEtiNDFGLzRFL3R2cGJBdEdneDE5VzYyb0IwcG83M2JKQjN2bUIzTzZpYzBna2VQS1dxUEFFakxINAo0MVQ0U2MxbU5qT0p2TC82Z2ZXZDRqUEllMTRFWDM5UW8xeEtPSkh0dDU4Qm5yaVZwZVBWUFprRklOYWwrRTJECk1rbzI1dzkzd3FsSDBuVEF4eHlFQ3RTNjdpS1BldVFPRXhLNlJOQlRBb0dCQU14em1jOTdHZmlPckU3dFo1YTUKMWd4K05Uc2oxbDdKV0lUaTQ5ZkdtdS9vVzhjS25hNVpTZFVXZzNsd0w4Wmh4QVZLM2FYbnFrdGVGUXZYdDBFZwpreU5KNWtSZ2p3Z0hwbUN0VVdwdTQrNDIxRVBBVExjcit3MmNZWm1QQkIrZDF1Z25YRVE2YXdETE5zUFZmb3ptClFQbmNrVlVVeCtsRGZYZ0M4Z05QYiswSEFvR0JBTnRmamlCMTcyT0pQMTl4OW94ekRVN0lLNTlKWm12OStMc0oKSWRWUGdHV2tValJwMHduV3p0ZTRiak91ck42dGVkQ0pNbXhiUWl3NGpFUFhuYkVEdHBpamRYdlFteEluUUdpZAo2RTd2MC9jYzd1R2w0UmNnVFJ0RmNiV0pXbU9HNlFrUGt4SGlTUXpDYjJZWGFSaEMxdlNQVW5UelRZUG1VMzFKCnlVdndYWEpkQW9HQkFMMC82Vlc1SGRsSkZCeHlTVGVBTVdVZ0lsVzNpSFhkeVFNRGdic3RPZHl2MittdkRJdEsKS2wzd0FPNitDZ25XNDAxek9yOFRkS2xsNC9paHdoakJaV3k4TWtXUnYrQUEyY21ZMjRRR2liY2cySjIrMkFneApRYllsbEkxWjR3dGVNVjVLTXdFS1FVaXdyTzhBVFZHZCt0QUx6MmRuZXEwNDZuRXFaa2NkZU93aQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+ # CA root certificate valid till 17.12.2049
+ ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURrekNDQW51Z0F3SUJBZ0lVR1lXL0t2Mmo0ZTY4UkZ3S2hJTjEzYVkyRStNd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRRNU1USXhOekF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQXY5Vk4vdldzalJTZ2dsd2NBRXR2TmN5NVNiRXZkMk9aSXFMa2lVMmZQWjgvUG5QZHorNnQKZ3d6UEtzczh5bkowUEVYeEt2TDMzSWtrbFNueDFCL0lSaHNlU3VFWUxjVUZXdys2MFFpb2Q5ZlJESEFaeFhCVQpaTGlUaUJFMkFEY2lvWnQ3cjBlZGV6cHM5dEUyU3RwZ0N3dmpGeE5JZGxBUkJTN0o3eTRiOTZ3Y3lzdHFOV1luCnI2eFRCYUovTE8xM29jN1V3ZUdRa0xaSy95OG5hOWR5cTlGVDdFbFZVbFEyT0ptYXErdENiK2NLK2hOcWhMaEoKcmpXemJjVHdTRmtNQWt3eDVHUGp2RHRJak5seEpFY1BsWlA2M0tSZEhyWnZWeXEyQllxUkhCb0VHT3F0ZmpYQgpTZkV4bFNLMXVZQ1NkU2oyMFQ4YUJ5Q1dqTEhiL3hrZFRRSURBUUFCbzFNd1VUQWRCZ05WSFE0RUZnUVVqTjRiCnVaRWg3SkFBaG9pdDE4ZXJXM2RlVFBnd0h3WURWUjBqQkJnd0ZvQVVqTjRidVpFaDdKQUFob2l0MThlclczZGUKVFBnd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBTkJna3Foa2lHOXcwQkFRc0ZBQU9DQVFFQWtSRGhlbHYvT3IvbwpQRzB1S3hReVVvMXJvRlhFNmRnN2tSd1FsYjhNbnlVQmZZWkZrWHFSU04yRGZKaFZ1OTVoWnR4RXVobTBKbjRSCmNKUzV5NUZHNmF5WmZJN3krN0Yvdk9pb0RZYUNWWXluWkRZRlluOENsZld1aWplbW1JSStUTEZIUnFiOEJQUDIKSVRtUGl1VkhEeWMrbUdmMU95WmVONnc5bTZRL0FmdEJ1d3R6Qmt3MGlQUFlHVHZFSFdiNkRWWGIyUjBFYnBJUApPYWsveUtUZ2VOR1htemY2ZUhqZnkxaVVla0VXV1N3YWhkSlo1WFYzbGRUY3Q4bmRwM0NRdHZ1Z3YzQzl6T3A5CkxnMUlGR1JhVElIU2NvOWhheWQ2eEtlL0kvTXY1OStHeTlITDhqeGJxMWpMbzFneEN0Mi9KUWZZRmxpb3RkVGMKQ0VJVjVNNGt2dz09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml
new file mode 100644
index 00000000..aa8e6027
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/credentials/mtls-cert-secret.yml
@@ -0,0 +1,13 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-cert-secret
+ namespace: test
+type: Opaque
+data:
+ {{- $files := .Files }}
+ {{- range tuple "ca.crt" "server.crt" "server.key" }}
+ {{- $path := printf "certs/positive/%s" . }}
+ {{ . }}: >-
+ {{ $files.Get $path | b64enc }}
+ {{- end }}
diff --git a/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml b/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml
new file mode 100644
index 00000000..a57dfb87
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/mock-app/templates/mock-app.yml
@@ -0,0 +1,65 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ labels:
+ app: {{ .Values.global.mockServiceName}}
+ name: {{ .Values.global.mockServiceName}}
+ namespace: {{.Values.global.namespace}}
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: {{ .Values.global.mockServiceName}}
+ template:
+ metadata:
+ annotations:
+ traffic.sidecar.istio.io/includeInboundPorts: "*"
+ traffic.sidecar.istio.io/excludeInboundPorts: "8090,8091"
+ labels:
+ app: {{ .Values.global.mockServiceName}}
+ spec:
+ containers:
+ - image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.mockApplication) }}
+ name: {{ .Values.global.mockServiceName}}
+ ports:
+ - containerPort: 8080
+ - containerPort: 8090
+ - containerPort: 8091
+ imagePullPolicy: Always
+ volumeMounts:
+ - name: certs-secret-volume
+ mountPath: /etc/secret-volume
+ - name: expired-certs-secret-volume
+ mountPath: /etc/expired-server-cert-volume
+ livenessProbe:
+ httpGet:
+ path: /v1/health
+ port: 8080
+ initialDelaySeconds: 3
+ periodSeconds: 3
+ volumes:
+ - name: certs-secret-volume
+ secret:
+ secretName: mtls-cert-secret
+ - name: expired-certs-secret-volume
+ secret:
+ secretName: expired-mtls-cert-secret
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: {{ .Values.global.mockServiceName}}
+ namespace: {{ .Values.global.namespace }}
+spec:
+ selector:
+ app: {{ .Values.global.mockServiceName}}
+ ports:
+ - name: "http"
+ protocol: TCP
+ port: 8080
+ - name: "https"
+ protocol: TCP
+ port: 8090
+ - name: "httpsexp"
+ protocol: TCP
+ port: 8091
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/Chart.yaml b/tests/resources/charts/gateway-test/charts/test/Chart.yaml
new file mode 100644
index 00000000..ecec00c5
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/Chart.yaml
@@ -0,0 +1,24 @@
+apiVersion: v2
+name: test
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+# It is recommended to use it with quotes.
+appVersion: "0.1.0"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl b/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl
new file mode 100644
index 00000000..9cba1391
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/_helpers.tpl
@@ -0,0 +1,12 @@
+
+{{/*
+Create a URL for container images
+*/}}
+{{- define "imageurl" -}}
+{{- $registry := default $.reg.path $.img.containerRegistryPath -}}
+{{- if hasKey $.img "directory" -}}
+{{- printf "%s/%s/%s:%s" $registry $.img.directory $.img.name $.img.version -}}
+{{- else -}}
+{{- printf "%s/%s:%s" $registry $.img.name $.img.version -}}
+{{- end -}}
+{{- end -}}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml
new file mode 100644
index 00000000..19977c60
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/code-rewriting.yaml
@@ -0,0 +1,47 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: code-rewriting
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Code Rewriting
+ skipVerify: true
+ labels:
+ app: code-rewriting
+ services:
+ - displayName: code 500
+ name: code 500
+ providerDisplayName: code 500
+ description: Should return 502 given 500
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/500"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-500"
+ - displayName: code 503
+ name: code 503
+ providerDisplayName: code 503
+ description: Should return 502 given 503
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/503"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-503"
+ - displayName: code 502
+ name: code 502
+ providerDisplayName: code 502
+ description: Should return 502 given 502
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/502"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-502"
+ - displayName: code 123
+ name: code 123
+ providerDisplayName: code 123
+ description: Should return 200 given 123
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/123"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/code-rewriting/code-123"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml
new file mode 100644
index 00000000..1507127e
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-negative.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: basic-test-negative-case
+ namespace: kyma-system
+type: Opaque
+data:
+ password: {{ "passwd" | b64enc }}
+ username: {{ "user" | b64enc }}
+
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml
new file mode 100644
index 00000000..311f41fb
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/basic-auth-positive.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: basic-test
+ namespace: kyma-system
+type: Opaque
+data:
+ password: {{ "passwd" | b64enc }}
+ username: {{ "user" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml
new file mode 100644
index 00000000..f2025ecb
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-case.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-negative-case
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/positive/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/positive/client.key" | b64enc }}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml
new file mode 100644
index 00000000..b8e71c3b
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-client-cert.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-negative-expired-client-cert
+ namespace: kyma-system
+type: Opaque
+data:
+ # Client certificate expired on 02.08.2022
+ crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVZldyTzlHRG1rR0FCRktMdzYvL0tVcDdYMnE0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTZSMU1VeVRTT2FyNGtPTUdMV1BWZUU4WXkwRTRlcGV3NS9aL2ZZNlQ5d1BWYUdSTE9RSEYKLzhCVjVEeHhGLzN0K0Y0S21tSHJ6TjlyUDN5T3VnVnY4bEhlek9VeFhmZWFrN0hMR2VGV3k5TGxmc3BtODhJMwpxR3BHM2FaUUhZS0VGRk1IUDREcGdkM3JUUVlhNVFnMDRNZDZuZjk1SFY1YzJtTlErb2pqUWt0cWROSUpPb2p5Clk4WjdlaWMyRURtMWxqYkdCTFZESEY1aUY3QjQ1bXp0OTgzZElaeC81TW9OMnpjdlF5OU9GMlkvcm1EL284QkQKd3llR2xvejVqTHNGRUJqbWdBSWFrSXBiVkZqekk4Tlp3cW0wOTZSM2JuZCtJdml3eS96YkRxYlNxQ2Qvdi9qRApIT0x2M0pIRXNLL0VxNng0VGZhL1lqTFozbjRrNmhCVHVRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxpMVI2MjhpOXZXWkkrNnJKZHN4MEhtZUZLUGNVenV3R1ptU2t2TC9rTHliOUE3b2V6SQpPbkRLL0EzOFpGcmpzTE9YQWRJZkxXS0laMkh5b21FNG9HMldNL3phN1BCWDc4dXpycWw5bFR2eWtQOFRkdm5qCkpTN2l4cWZucW1UNDdLVHFOSjRleXMzd2NOU29kTHlydWNDUWlLeUllMEFUQ3RUSUY2WnNGR3Q2WkN0WS84czYKQTJ0Rk92dmhTWm01UytsZGVEQ3FMajJMOW1oZEY0RzlMRmJ5Q2pTbVNDWEFSbDVpanBjb2pEWEVDRTdobTk5YwpnbU1scjE2Uzh1Nm5mWEVRdXFwTys4MU4xTEVpTzVyUzRwUGxBdGgyVk5YOXc0WHBmbEM1VDA0bzA5S21HYzhLCjhVTjB1YXJpRGx2NW93SEZlWVRsZkZ6eGhUUDJBOWFPL3U4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+ key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNlIxTVV5VFNPYXI0a09NR0xXUFZlRThZeTBFNGVwZXc1L1ovZlk2VDl3UFZhR1JMCk9RSEYvOEJWNUR4eEYvM3QrRjRLbW1IcnpOOXJQM3lPdWdWdjhsSGV6T1V4WGZlYWs3SExHZUZXeTlMbGZzcG0KODhJM3FHcEczYVpRSFlLRUZGTUhQNERwZ2QzclRRWWE1UWcwNE1kNm5mOTVIVjVjMm1OUStvampRa3RxZE5JSgpPb2p5WThaN2VpYzJFRG0xbGpiR0JMVkRIRjVpRjdCNDVtenQ5ODNkSVp4LzVNb04yemN2UXk5T0YyWS9ybUQvCm84QkR3eWVHbG96NWpMc0ZFQmptZ0FJYWtJcGJWRmp6SThOWndxbTA5NlIzYm5kK0l2aXd5L3piRHFiU3FDZC8Kdi9qREhPTHYzSkhFc0svRXE2eDRUZmEvWWpMWjNuNGs2aEJUdVFJREFRQUJBb0lCQUhWalJZNFEyclFqZm13bgpobkxROVN4U1dGL3lCZWpsL2pXeEVWNCtzQkFScENPZmJhblZWTW1ISnpsNW5sSEFrMWNndENJdDhUb0h2OUFHCmZ6RDVqL2ZzZGsramtvcUpKeFA4MGhQRVA1c0FKb1VFazNkb2MvS2hJZkozejV3c255cEU3VDl6UVNNZWgyRVEKRS9jRmZPczhTR2pMdjBla3Z3bFNQZk1MZjdWZm9vK2h6K2krUW5jMXFRR2lPVW90Tk11WmoxcU85Mm5Ib2toMApxcWRRWlFOa1pUQm1sd2Y0bGNpeVJsTVUwRHJJNmd1bDNRazNpd2hueU1Kb2h4dVJxT3RSNzJjTS9SVFpsMHdWClh4WmJ5VkM1R2dhRUR0aUtFaE9xdmpPN2N4WU9BU3U3aGVDbWRnRFdMMGFSVjE1ai9Sb0twRUtkeWt0SHFrRjQKcWVLZndBa0NnWUVBL2pKaHBqSWh5N09Vajh1cW1scHE4L0tyRG5zUG1FK0VqbzFxOWdiTDByRVZSaktMNHpLaAp2SDdrdHJjQUkrMkpaS0hlSDVmVUJSMVYxK3R5WXM5OHUwOFpHSmQ0TFdjNjFFUTQvbEtFVUhaUU41TnpJOHprCmhWbjBhNWZOQjlLMDQ4aU1XQTh6ZlgyQ2JvWWltT0V3ZC9BZUtwdzZjN0E0UXU5RVRGckNhMGNDZ1lFQTZzU2gKb2V4U0pxbE9RMjk1VjlVWWtNTkkrNkR1ZGNoSUZkamY2bUp2NEo1aGhQSmJDY1doZkZjdnZmNkQ0SzBDei8rVwpybmhreWNlU1dDRHNuN3laN1VBbHAwVXp2bk1IYXBqUXUwN3JpcGI0UGExdlE2MkxRYWhTWnFJZmkxeFVqQ1NmCm1GcDJZSXVRV2FWOWFlYUdmSnY0anRnZUJ6VUxFYngwMGdpb3lQOENnWUVBOUR5Q09JWm9sR2xxYjdOWHExRCsKL0grSVBiU2Q2bEZVNHdjYjQySHFTdmtjb01NR1Iza3BqNHc0d3hvWUIyMC9Hckt3VXBpMS9XZ1BTQlFRWnNKSApiVTExcG53NjJ4MFptRVFvb3F1ME4vOUYyZkJScSs4OURxZTh3ZmdyNXIxY1VwUXB6SjVtY2NlN0graS9xemFMCk5HSkJDZDNzQjZZa21LTitjd0t0VlJjQ2dZQkRKaFM1RkxmMm1PeHF1Mkt3cmFIR0hpVXMyNzM0OEYwMTZuODUKTWdpZjdZMGxFcERaZmE2UHV2eEwwcFZ6Mk9oNkI3ZllsVlQycGQrRTEzMzJ2bUlraXZsNkc0QU9WQ1psNWVtbAorWS9EWnlUL3R6Q2c0ZTEzelNZc2R1aWcycnJRRHRXYkpSekF4b3AyS2JCeWJ0NCttL24vR1crVlRpV3BZQWJsCjRGWXVqd0tCZ1FDT3VUenVZS1QrVHZuU3hDVWdZbUN3SWs5MlVxUHJCS3J5UE1sekxMdFBaMlF3UjNpRGlWTzEKUlpxNGtkc1NzU05SNUJKUzlrVW92dUpubG1hb0ZBM2NLZXhRdk9mcmFVdXNGcXVHb3NWR3hrSGo3S24ydnFadQppK0NDNTZPNHRRMnNhOXZ6MFNpTWZoMzdFRlh6eUFVODNpWGY2aTB0MGlzb0cvYmFpTlNDZ2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml
new file mode 100644
index 00000000..e96af97d
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-expired-server-cert.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-negative-expired-server-cert
+ namespace: kyma-system
+type: Opaque
+data:
+ # Valid client certificate expiring on 17.12.2049
+ crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1EZWMgMTcgMDA6MDA6MDAgMjA0OSBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVWnVOZ3FCQTdSUEs4bStSSnhuaXRTS21xZFdjd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRRNU1USXhOekF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTRpZHp6dFhjZG9Dd21YNm9HeSt5elFjYW5iZVpmZ2xBTFlrVktUUVhrYXoyWW8wWXV3cGQKS1VhSjZEbkVUTVVneGYwb3RUWENBSU9HM1cySTZyRzc1UXBsVWZTNDVzNWQ4MVE5WGhMRm9Oa0o1ZWlzUW1paQpCSURQOC9qQ2RHSk04Z2hpa1A1bk1xQ0hRdGk1bkR3MmtINCtiZDNIVzgyWTFWWkg5Y3ArdUpDa251clMvbDArCnFSUkxndWZUY0c0VU5iaGEwWnZaNUcwMStTdnJxVXBudmVaQUdlcTlOaWlhUXhmSkFkcTdISHMrMnFDOGtEczQKUllOVFJNTUMrYXVueDV3M1g4eEUyR0oxRkdjSjNmRkszTFhsNnRXaWFBajVHNjloSnpoNG5HMzdEVy9aMEF0UgovM3B2UnB2K0k1WCtTUWZUVHBqQzBGbld6Z1JrZ2hucWV3SURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxQSmVJNGtGRlV4YlNVN2NtcHFjaFpWWUx0dmhYRkU4Y0YrUUZTRzhTOXZIOUVxdnlYNApNNW8wSkQzeDU3T0dnK0liVHJRQXRXVG5zbnNBbGk4d3FMQTJVYlZZTThOM0JRSFBSNHZUMkZjRndHQmpQR3hGCmlGcDZlUGxvbTJ0bTBEVjNXbzkzdG0wWnhmRXpQYXlHOHJhc0puc3psT0hVOEtqT2ppOUtjN0F4WXBhODh4MDIKdTN4RjNUMkhZMkExeS9NdmJpeFpwNG12YWZiTFJOOUFqUWY3QXJxNEQ5cThlS0ZhY3EwYndFaVlTK3daZG9kNQp0RDM5MktweVNPZDBFMld4dHozNTRsZmI0cEIzK2k5Z21Ma3lUbUhHZWJzMWdBUmV1TENQTW5OQThTZTdHVGNkCjA0ZlltR3pmNjVySDNqM1d2MzQ0dy81M2dtRWdrOXg0TGswPQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+ key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcFFJQkFBS0NBUUVBNGlkenp0WGNkb0N3bVg2b0d5K3l6UWNhbmJlWmZnbEFMWWtWS1RRWGthejJZbzBZCnV3cGRLVWFKNkRuRVRNVWd4ZjBvdFRYQ0FJT0czVzJJNnJHNzVRcGxVZlM0NXM1ZDgxUTlYaExGb05rSjVlaXMKUW1paUJJRFA4L2pDZEdKTThnaGlrUDVuTXFDSFF0aTVuRHcya0g0K2JkM0hXODJZMVZaSDljcCt1SkNrbnVyUwovbDArcVJSTGd1ZlRjRzRVTmJoYTBadlo1RzAxK1N2cnFVcG52ZVpBR2VxOU5paWFReGZKQWRxN0hIcysycUM4CmtEczRSWU5UUk1NQythdW54NXczWDh4RTJHSjFGR2NKM2ZGSzNMWGw2dFdpYUFqNUc2OWhKemg0bkczN0RXL1oKMEF0Ui8zcHZScHYrSTVYK1NRZlRUcGpDMEZuV3pnUmtnaG5xZXdJREFRQUJBb0lCQVFDWUFySzUzVkFocXlDSgpHL1E4eWRQaU1oczIxZGpyT2FhVXRPYXZXbDlaUUt3ZjAvMUNnNVhaRDV2VXB6ZUY3cDYzMWhGTnRFT2hlc2JsCkFTSWR0cmU0SFVPN1VjWVRCYlZxd0QyN2hOeW40QnJpR1lIbjVWSzV1aWVOTXJEcDc4VU9qb3BLTVdZR1JwYUUKWFE1dHNKOXdnaHJPV0ZzUEh1UFN5ZnIyZ0ZTckV2TEdYRUQxUVBFdUhqcllZeDBMcVJDVSt4NUhvajBkLzZyYgpOMEJjako5SHVwMDZLejN4QlBxemgyeHp1Q2kyYjdGc01vQndkTGcyNGM3bHJsZCtLb09ZdFczLzJSZUJPdXpHCk1HWjFPZlJJcERjdk9xNXlBWXZuYjcya0xJV3E2ZUxCVkx2ekFJZnBLRFBtbmVkd0p2dXVpcUZldktzQVY5eXAKSlo1NjdXeXBBb0dCQVBPbHZvT3pxRUwrN0c4RUM5TW9Vc0lOWVRMMXdYU3VUTzVvczlpc0doRzJHNlprajRWWgpPNlY4Qm1ET2tYT0NtRzNpTWY4M0NSVGs3Um5MVExiRVJJRkxzNFpnend0N2trVHVXSnlzTkRUeGZuMmFVeUlwCm9iNWFvWVozcHNiTXBIbmxNSGN2R1l4SXFVK0JzQVpnbGtOYTdSQVVwTnN5bkJWYzNpZEV5R0hmQW9HQkFPMmUKcWx1dlI4b096VC9jbzVCS1pWS05jQ2V2bHhJNmVXd1NrRDJaaGNuZU1aWFcyY3Z5NERzUWFHQWNKelJsaWpvNAo0RXFsN2VQM0VYbVZYNEtQWXlkSFpZWGZubEkwWVdrSHloR015anBueWFYUWZ2c3AyU1BpM0lKQTZCZ2htbThKCkxyMWFTcmtLaXE3bis3dmtVYitESktBSk0wZXoyc1dzTkNMMC9XTGxBb0dCQUpkcU1YTjNldUhudXRkakZGWXQKZ1FESGY5aERrZTRKUkJZRlMzOGp0Uys4bElKYmpEVzZ0cTZvM08zY2NkZnZHUHR3enRGa1NtaUp2QytEZ0RFMAoxNzNpWmJibEFzYUlET1o1bU9nRXZJMEtaeWwzZHFLTWJNLzNVdHBXRVhjS1JremFlYndYc1REVkZ5TXAzVktaClE4aW9BUnMxT1I1ZjNWQUpYcVhZd1E3UkFvR0Fid0ZvWkZ5R0ZRYkZLOGhQUU9FQVpJaGVsS3VhejVFeG1DTXoKN3hNQlJVVGZ0VGdobHYxbmN6QS9FbWNVaVkzRi9WMEVxdHJKUDIzMFkvQThKaW9HRUJ0eWVnLzFUa0hhSDg3Ygp2MGNlVWhxYVFUUWRuZ2Yyd0tVQ2puYno5aEg4cTFLRzJ6NkxHZGFxNHZyTXh3SHFqcVVkUHdZTlJybm13ZUdvCm1Zd0pzMkVDZ1lFQXROSHI2TEx6VkVIaWU3MzhZV0doYnRSUGRPWGdFMStqVTdhZGZ4RXJtckVLL0ZZdkl5dmoKVWZ1WHlrZ3ZBeWF1Q2tiS2RsRFZkd1d5K2NuTXNCSkFSbFY5a1BGK2xwaTVweUpBL2J6blF4VVJHYmZ1UHNkUQpHZmRjUUhyMjVjR0xxN3E2Z0xaRUJxS0JpMmFZc3BkcmtNQTFIY0txNDhmVUNtNmZyMGVyelVzPQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml
new file mode 100644
index 00000000..6ae6ceac
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-negative-other-ca.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-negative-other-ca
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/negative/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/negative/client.key" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml
new file mode 100644
index 00000000..cce16c4f
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-incorrect-clientid.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-negative-incorrect-clientid
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/positive/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/positive/client.key" | b64enc }}
+ clientId: {{ "incorrect" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml
new file mode 100644
index 00000000..3bbb0851
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-nagative-other-ca.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-negative-other-ca
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/invalid-ca/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/invalid-ca/client.key" | b64enc }}
+ clientId: {{ "clientID" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml
new file mode 100644
index 00000000..02377d6e
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-case.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-negative-case
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/positive/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/positive/client.key" | b64enc }}
+ clientId: {{ "clientID" | b64enc }}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml
new file mode 100644
index 00000000..743fd941
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-client-cert.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-negative-expired-client-cert
+ namespace: kyma-system
+type: Opaque
+data:
+ crt: bm90QmVmb3JlPUF1ZyAgMSAwMDowMDowMCAyMDIyIEdNVApub3RBZnRlcj1BdWcgIDIgMDA6MDA6MDAgMjAyMiBHTVQKLS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURkakNDQWw2Z0F3SUJBZ0lVZldyTzlHRG1rR0FCRktMdzYvL0tVcDdYMnE0d0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREV3TUM0RwpBMVVFQXd3bmJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNNQjRYCkRUSXlNRGd3TVRBd01EQXdNRm9YRFRJeU1EZ3dNakF3TURBd01Gb3dXVEVMTUFrR0ExVUVCaE1DVUV3eENqQUkKQmdOVkJBZ01BVUV4RERBS0JnTlZCQW9NQTFOQlVERXdNQzRHQTFVRUF3d25iVzlqYXkxaGNIQnNhV05oZEdsdgpiaTUwWlhOMExuTjJZeTVqYkhWemRHVnlMbXh2WTJGc01JSUJJakFOQmdrcWhraUc5dzBCQVFFRkFBT0NBUThBCk1JSUJDZ0tDQVFFQTZSMU1VeVRTT2FyNGtPTUdMV1BWZUU4WXkwRTRlcGV3NS9aL2ZZNlQ5d1BWYUdSTE9RSEYKLzhCVjVEeHhGLzN0K0Y0S21tSHJ6TjlyUDN5T3VnVnY4bEhlek9VeFhmZWFrN0hMR2VGV3k5TGxmc3BtODhJMwpxR3BHM2FaUUhZS0VGRk1IUDREcGdkM3JUUVlhNVFnMDRNZDZuZjk1SFY1YzJtTlErb2pqUWt0cWROSUpPb2p5Clk4WjdlaWMyRURtMWxqYkdCTFZESEY1aUY3QjQ1bXp0OTgzZElaeC81TW9OMnpjdlF5OU9GMlkvcm1EL284QkQKd3llR2xvejVqTHNGRUJqbWdBSWFrSXBiVkZqekk4Tlp3cW0wOTZSM2JuZCtJdml3eS96YkRxYlNxQ2Qvdi9qRApIT0x2M0pIRXNLL0VxNng0VGZhL1lqTFozbjRrNmhCVHVRSURBUUFCb3pZd05EQXlCZ05WSFJFRUt6QXBnaWR0CmIyTnJMV0Z3Y0d4cFkyRjBhVzl1TG5SbGMzUXVjM1pqTG1Oc2RYTjBaWEl1Ykc5allXd3dEUVlKS29aSWh2Y04KQVFFTEJRQURnZ0VCQUxpMVI2MjhpOXZXWkkrNnJKZHN4MEhtZUZLUGNVenV3R1ptU2t2TC9rTHliOUE3b2V6SQpPbkRLL0EzOFpGcmpzTE9YQWRJZkxXS0laMkh5b21FNG9HMldNL3phN1BCWDc4dXpycWw5bFR2eWtQOFRkdm5qCkpTN2l4cWZucW1UNDdLVHFOSjRleXMzd2NOU29kTHlydWNDUWlLeUllMEFUQ3RUSUY2WnNGR3Q2WkN0WS84czYKQTJ0Rk92dmhTWm01UytsZGVEQ3FMajJMOW1oZEY0RzlMRmJ5Q2pTbVNDWEFSbDVpanBjb2pEWEVDRTdobTk5YwpnbU1scjE2Uzh1Nm5mWEVRdXFwTys4MU4xTEVpTzVyUzRwUGxBdGgyVk5YOXc0WHBmbEM1VDA0bzA5S21HYzhLCjhVTjB1YXJpRGx2NW93SEZlWVRsZkZ6eGhUUDJBOWFPL3U4PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
+ key: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBNlIxTVV5VFNPYXI0a09NR0xXUFZlRThZeTBFNGVwZXc1L1ovZlk2VDl3UFZhR1JMCk9RSEYvOEJWNUR4eEYvM3QrRjRLbW1IcnpOOXJQM3lPdWdWdjhsSGV6T1V4WGZlYWs3SExHZUZXeTlMbGZzcG0KODhJM3FHcEczYVpRSFlLRUZGTUhQNERwZ2QzclRRWWE1UWcwNE1kNm5mOTVIVjVjMm1OUStvampRa3RxZE5JSgpPb2p5WThaN2VpYzJFRG0xbGpiR0JMVkRIRjVpRjdCNDVtenQ5ODNkSVp4LzVNb04yemN2UXk5T0YyWS9ybUQvCm84QkR3eWVHbG96NWpMc0ZFQmptZ0FJYWtJcGJWRmp6SThOWndxbTA5NlIzYm5kK0l2aXd5L3piRHFiU3FDZC8Kdi9qREhPTHYzSkhFc0svRXE2eDRUZmEvWWpMWjNuNGs2aEJUdVFJREFRQUJBb0lCQUhWalJZNFEyclFqZm13bgpobkxROVN4U1dGL3lCZWpsL2pXeEVWNCtzQkFScENPZmJhblZWTW1ISnpsNW5sSEFrMWNndENJdDhUb0h2OUFHCmZ6RDVqL2ZzZGsramtvcUpKeFA4MGhQRVA1c0FKb1VFazNkb2MvS2hJZkozejV3c255cEU3VDl6UVNNZWgyRVEKRS9jRmZPczhTR2pMdjBla3Z3bFNQZk1MZjdWZm9vK2h6K2krUW5jMXFRR2lPVW90Tk11WmoxcU85Mm5Ib2toMApxcWRRWlFOa1pUQm1sd2Y0bGNpeVJsTVUwRHJJNmd1bDNRazNpd2hueU1Kb2h4dVJxT3RSNzJjTS9SVFpsMHdWClh4WmJ5VkM1R2dhRUR0aUtFaE9xdmpPN2N4WU9BU3U3aGVDbWRnRFdMMGFSVjE1ai9Sb0twRUtkeWt0SHFrRjQKcWVLZndBa0NnWUVBL2pKaHBqSWh5N09Vajh1cW1scHE4L0tyRG5zUG1FK0VqbzFxOWdiTDByRVZSaktMNHpLaAp2SDdrdHJjQUkrMkpaS0hlSDVmVUJSMVYxK3R5WXM5OHUwOFpHSmQ0TFdjNjFFUTQvbEtFVUhaUU41TnpJOHprCmhWbjBhNWZOQjlLMDQ4aU1XQTh6ZlgyQ2JvWWltT0V3ZC9BZUtwdzZjN0E0UXU5RVRGckNhMGNDZ1lFQTZzU2gKb2V4U0pxbE9RMjk1VjlVWWtNTkkrNkR1ZGNoSUZkamY2bUp2NEo1aGhQSmJDY1doZkZjdnZmNkQ0SzBDei8rVwpybmhreWNlU1dDRHNuN3laN1VBbHAwVXp2bk1IYXBqUXUwN3JpcGI0UGExdlE2MkxRYWhTWnFJZmkxeFVqQ1NmCm1GcDJZSXVRV2FWOWFlYUdmSnY0anRnZUJ6VUxFYngwMGdpb3lQOENnWUVBOUR5Q09JWm9sR2xxYjdOWHExRCsKL0grSVBiU2Q2bEZVNHdjYjQySHFTdmtjb01NR1Iza3BqNHc0d3hvWUIyMC9Hckt3VXBpMS9XZ1BTQlFRWnNKSApiVTExcG53NjJ4MFptRVFvb3F1ME4vOUYyZkJScSs4OURxZTh3ZmdyNXIxY1VwUXB6SjVtY2NlN0graS9xemFMCk5HSkJDZDNzQjZZa21LTitjd0t0VlJjQ2dZQkRKaFM1RkxmMm1PeHF1Mkt3cmFIR0hpVXMyNzM0OEYwMTZuODUKTWdpZjdZMGxFcERaZmE2UHV2eEwwcFZ6Mk9oNkI3ZllsVlQycGQrRTEzMzJ2bUlraXZsNkc0QU9WQ1psNWVtbAorWS9EWnlUL3R6Q2c0ZTEzelNZc2R1aWcycnJRRHRXYkpSekF4b3AyS2JCeWJ0NCttL24vR1crVlRpV3BZQWJsCjRGWXVqd0tCZ1FDT3VUenVZS1QrVHZuU3hDVWdZbUN3SWs5MlVxUHJCS3J5UE1sekxMdFBaMlF3UjNpRGlWTzEKUlpxNGtkc1NzU05SNUJKUzlrVW92dUpubG1hb0ZBM2NLZXhRdk9mcmFVdXNGcXVHb3NWR3hrSGo3S24ydnFadQppK0NDNTZPNHRRMnNhOXZ6MFNpTWZoMzdFRlh6eUFVODNpWGY2aTB0MGlzb0cvYmFpTlNDZ2c9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
+ clientId: {{ "clientID" | b64enc }}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml
new file mode 100644
index 00000000..2a536df0
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-negative-expired-server-cert.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-negative-expired-server-cert
+ namespace: kyma-system
+type: Opaque
+data:
+ crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUQwRENDQXJpZ0F3SUJBZ0lVSkV2L3RtUndLOXRzdWhpQVlaT2Q4NjN1UHlBd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1pqRUxNQWtHQTFVRUJoTUNVRXd4Q2pBSUJnTlZCQWdNQVVFeEREQUtCZ05WQkFvTUExTkJVREU5TURzRwpBMVVFQXd3MGJXOWpheTFoY0hCc2FXTmhkR2x2Ymk1MFpYTjBMbk4yWXk1amJIVnpkR1Z5TG14dlkyRnNkR1Z6CmRDMXZkR2hsY2kxallUQWVGdzB5TWpBeE1ERXdPVEV3TVRCYUZ3MHlNakF4TURJd09URXdNVEJhTUdZeEN6QUoKQmdOVkJBWVRBbEJNTVFvd0NBWURWUVFJREFGQk1Rd3dDZ1lEVlFRS0RBTlRRVkF4UFRBN0JnTlZCQU1NTkcxdgpZMnN0WVhCd2JHbGpZWFJwYjI0dWRHVnpkQzV6ZG1NdVkyeDFjM1JsY2k1c2IyTmhiSFJsYzNRdGIzUm9aWEl0ClkyRXdnZ0VpTUEwR0NTcUdTSWIzRFFFQkFRVUFBNElCRHdBd2dnRUtBb0lCQVFERzFDaHE2WmI3eUdaS0ZwUmgKV01sUDh6L0NtZEFZbktpRkxKNVhpMFlXakpZZXlUSUdXRWM4bm5SK1VOalZKejZjN0w5cCs4bFEvK1ZGZUpmRwpmYTM4WnhHQVZxNitpcXdJOVpwSTd2SlVZRnd0SDJLQXA5cDdZRHdOVllUbXZEb3lyTFh3L1c3c1p1Q2c3QWVBCkh5MGlBcGtBQkk1Tmg4dWFMNkk3eE8xK0dCSVhNYVkzNlFDZXpvejdvbkoyUURBVjlybVEzQndpZUtwMkNUV1QKS3ZHaVBac04rTlVLbndjdVoxbThpUHQxYmozaGsvS3p1MGxvR2RVaXJiOFFUNkFzRnJ2ZzdGNFBYVmFsb1FSNwpJMy9paWpxZFBERmlEOHVwOXltdE9ja2tjQlorSmhqVDI4OTZxcFIzQVJVaGl0dXhreENpMUZHeEd1YlJNREFpCitmcExBZ01CQUFHamRqQjBNRElHQTFVZEVRUXJNQ21DSjIxdlkyc3RZWEJ3YkdsallYUnBiMjR1ZEdWemRDNXoKZG1NdVkyeDFjM1JsY2k1c2IyTmhiREFkQmdOVkhRNEVGZ1FVY25wWGg2b2xoei9tV01LTE1sVTR1Z0gxRUpNdwpId1lEVlIwakJCZ3dGb0FVMzRJa3V5V2NoVlN2ZXlNQXZmdDZOdE1JZkdVd0RRWUpLb1pJaHZjTkFRRUxCUUFECmdnRUJBQmNvYmVNSmkrQVZ1MGZtY0szYzM1RXhQU0pZTWlOYms1VXU0QmgzUlloY0tJeFFyUjY4T1FYT3ZzMEkKVVNaYmRkRjhrQU1KOFpwTGJWQmJnZ2NmUktrVUxPYko3TklyTEF2UmR6cVVzeThMc0VMZUM5cVlVM0E0TUFEWApHWU8zZjlWbnFIelpmRFNMTjFzOGoyK1JQU0hGL3lZbGxETWhCbkVJa1NwdXdWbXprMGFoZEtXRmthcG1xbEZMCkRSZ2JsTThMMVZTbGpXb3hGYUdLNUZFQ2dGZzJIcHJDc2o3SHl0TFR0KzEwc1F5ZU9vVktUVU9lNjhWeFlzR2kKbXd3bnhzTGtHdkdGWTNaVU9JQk5BY0ovdlJLSER4R1pPS29QblIrRmlodkJvZkR1RlBzbnRSTnBIRncrNVJsUQpjL1lUdThmQ1U4QnRpeGwvYkRxdTZUWmtyazg9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
+ key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktnd2dnU2tBZ0VBQW9JQkFRREcxQ2hxNlpiN3lHWksKRnBSaFdNbFA4ei9DbWRBWW5LaUZMSjVYaTBZV2pKWWV5VElHV0VjOG5uUitVTmpWSno2YzdMOXArOGxRLytWRgplSmZHZmEzOFp4R0FWcTYraXF3STlacEk3dkpVWUZ3dEgyS0FwOXA3WUR3TlZZVG12RG95ckxYdy9XN3NadUNnCjdBZUFIeTBpQXBrQUJJNU5oOHVhTDZJN3hPMStHQklYTWFZMzZRQ2V6b3o3b25KMlFEQVY5cm1RM0J3aWVLcDIKQ1RXVEt2R2lQWnNOK05VS253Y3VaMW04aVB0MWJqM2hrL0t6dTBsb0dkVWlyYjhRVDZBc0Zydmc3RjRQWFZhbApvUVI3STMvaWlqcWRQREZpRDh1cDl5bXRPY2trY0JaK0poalQyODk2cXBSM0FSVWhpdHV4a3hDaTFGR3hHdWJSCk1EQWkrZnBMQWdNQkFBRUNnZ0VBQlgrem1IVmFZQjlFT1BOVDZqZE81ZitudVVXUXZFV0U0WjRBeVJJSWY3SW0KcXJaTXhHRW5velVNcXJ1b3E0aDQwbFUzM0FJREtOTFM3KzlzWHloMXFkL2QyNHRLTE9uZjVTV0p2VStpY3hQeApLS3hRQ0pmYjBvS3dWbndSZjJJZ1IrdC80cWpYcXdFVFlFLzJ5eVBSbHptMEtveDF0UTQyNHM1RGNkeTU1cjFPCklNbEhPcXhFcE84YWtiY2ZacHE4cVdqMUFkU2VCRk5WdXJQaUxmQjMxN2pQd25RSkp2MU9NWWxsQXJWa1NDcisKNDlMcmk1QjRBL0UrQkh6SnhCQ1lyaC9IRCtCVlhxY3JDSDBKNEQ3TWEwVWJWMER5Vml5UWtwK2c5MGhoQ21HMwpnYVpKUmdkczJnQzI2cGRlR0E1and2Tjd3ZUYveUtsRVEwZXRib0NHS1FLQmdRREtJYUN3czVHbVZUYlRlaDJ0CnRFWmY1MWZwNVdRL1R2V1VJaWtsdHBXU1RRUHQwQWVtZFJxL1FQak5OS3YycnR0dDZIT0RHcW9iSnFVTENjaGYKbEU1cmc0N09EYnV5OHVPWmtVOEYyMTRjZDlSbnU3alNzcFd3ZkEwakpndFhiblZ2aVlXeUtjam90QVQwTXhQcgpON1VqV2dva20yQWg3amRMU2hvMFF4bkkwd0tCZ1FENzBUVzNCa2llYUdzN3IzY3hhUytvd1VoaUdMTnEyajN4Ckd2QmtXVmx5TzM5aHlCcm5aUk92ak9HRFE4eCtxSmRTN2V6Q2xGVmpsanc2V2dra1V6U1ZQSHkrb094RjE5T3UKM00xalhCZC9PMUh6VU1WUkcvYmJxaTJZeThhb0gyRWplbzE4VE9XbWhYUnFNb045TDV4Q3RkbDRnMGxMNnBJZgpPb3FlanltZHFRS0JnUURCZjZPbXhLQS96UCtwUHhPK1AvL0d1MTZycUU5cFU1dEFiZHRhSVFuYWZpT3V1eUUzCnRvOGVXNEpTWDRQbnFNaWkxSTRRQ2F5aVJVSmw2TDJLMGh5b1M4NmZid0lxY3Q1ekdtbTl2NXkrUC9CMFJYN1AKSk9xcmduWEpHaGh0WUc3SGthME5PM2I3WGFvSVpBVkRmWmJIK3VBTzN6Y09CRSttb1krb1REd1l4UUtCZ1FEcgo4TWpRZFEzRGhuaTYwcHZ1YXV6aHhEK3EwaFFCa1B5cWxLQWFsZkVON0J0ZEpkMjNZMmcvZXRPdFp2QUsyTEg0ClhMOFNUV040VE1LZnRjNk0vM3pzTzJGeVIxczUwWkFnYmZmdkdkRldQK0Y0QmZ6ckV6V0grZnFCQ0tWWXp4WDMKNVJMK0hScXJuSzFIOTQ1bDFCOG9EalQyQ3FTNWdjNXBmak4xZnhQeUNRS0JnQzVJR2FnbldPSG9XVEpnUG95YQptY1QrMk1NUGdJMzhaL2xzM0lmd1dIc2hUbE5sQ2k2RklnMkQrUUYrZk80ZGdYZS85K0dESERvM3J5MDU2Z2VhCng4T1FYZ2dxa0lJdkxDUnZGVlVId0M3Z0MyekpHakJ1V2dYUDhsYjRDNG4yemlQd1c5M1c2RkpXMnpwMzRacSsKU29JaUNXUEh6RXF3WjgxbEJwZ1VkVTZYCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K
+ clientId: {{ "clientID" | b64enc }}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml
new file mode 100644
index 00000000..1d83b2b2
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-oauth-positive.yaml
@@ -0,0 +1,12 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-oauth-positive
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/positive/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/positive/client.key" | b64enc }}
+ clientId: {{ "clientID" | b64enc }}
+
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml
new file mode 100644
index 00000000..f5540a3e
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/mtls-positive.yaml
@@ -0,0 +1,10 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: mtls-positive
+ namespace: kyma-system
+type: Opaque
+data:
+ {{- $files := .Files }}
+ crt: {{ $files.Get "certs/positive/client.crt" | b64enc }}
+ key: {{ $files.Get "certs/positive/client.key" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml
new file mode 100644
index 00000000..94ffed21
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-incorrect-id.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: wrong-oauth-test
+ namespace: kyma-system
+type: Opaque
+data:
+ clientId: {{ "bad id" | b64enc }}
+ clientSecret: {{ "bad secret" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml
new file mode 100644
index 00000000..13d0df23
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-negative-invalid-token.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: oauth-test-negative-case
+ namespace: kyma-system
+type: Opaque
+data:
+ clientId: {{ "clientID" | b64enc }}
+ clientSecret: {{ "clientSecret" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml
new file mode 100644
index 00000000..af54df50
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/oauth-positive.yaml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: oauth-test
+ namespace: kyma-system
+type: Opaque
+data:
+ clientId: {{ "clientID" | b64enc }}
+ clientSecret: {{ "clientSecret" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml
new file mode 100644
index 00000000..e6405b0d
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/redirect-basic-auth.yml
@@ -0,0 +1,9 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: redirect-basic
+ namespace: kyma-system
+type: Opaque
+data:
+ password: {{ "passwd" | b64enc }}
+ username: {{ "user" | b64enc }}
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml
new file mode 100644
index 00000000..f62520b2
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters-negative.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: wrong-request-parameters-test
+ namespace: kyma-system
+type: Opaque
+stringData:
+ headers: |-
+ {"Hkey1":["Wrong-value"],"Wrong-key":["Hval22"]}
+ queryParameters: |-
+ {"Wrong-key":["Qval1"],"Qkey2":["Qval21","Qval22","Additional-value"]}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml
new file mode 100644
index 00000000..847ac1ee
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/credentials/request-parameters.yaml
@@ -0,0 +1,11 @@
+apiVersion: v1
+kind: Secret
+metadata:
+ name: request-parameters-test
+ namespace: kyma-system
+type: Opaque
+stringData:
+ headers: |-
+ {"Hkey1":["Hval1"],"Hkey2":["Hval21","Hval22"]}
+ queryParameters: |-
+ {"Qkey1":["Qval1"],"Qkey2":["Qval21","Qval22"]}
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml
new file mode 100644
index 00000000..8a174203
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/manual.yml
@@ -0,0 +1,23 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: complex-cases
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Endpoints for complex tests
+ skipVerify: true
+ labels:
+ app: complex-cases
+ services:
+ - displayName: oauth-expired-token-renewal
+ name: oauth-expired-token-renewal
+ providerDisplayName: Kyma
+ description: Should renew the OAuth token after the expiration time
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ credentials:
+ secretName: oauth-test
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token?token_lifetime=5s"
+ type: OAuth
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml
new file mode 100644
index 00000000..1c8a7265
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/methods-with-body.yml
@@ -0,0 +1,40 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: methods-with-body
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: |-
+ Verify if methods, specified by `descritpion`,
+ are correctly forwarded, including their body
+ skipVerify: true
+ labels:
+ app: methods-with-body
+ services:
+ - displayName: post
+ name: post
+ providerDisplayName: post
+ description: POST
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/post"
+ - displayName: delete
+ name: delete
+ providerDisplayName: delete
+ description: DELETE
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/delete"
+ - displayName: put
+ name: put
+ providerDisplayName: put
+ description: PUT
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/echo"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/methods-with-body/put"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml
new file mode 100644
index 00000000..0b87dc65
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/missing-resources-error-handling.yml
@@ -0,0 +1,101 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: missing-resources-error-handling
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Missing resources
+ skipVerify: true
+ labels:
+ app: missing-resources-error-handling
+ services:
+ - displayName: application-doesnt-exist
+ name: application-doesnt-exist
+ providerDisplayName: Kyma
+ description: Should return 404 when application doesn't exist
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/where-is-app/idk"
+ - displayName: service-doesnt-exist
+ name: service-doesnt-exist
+ providerDisplayName: Kyma
+ description: Should return 404 when service doesn't exist
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/where-is-service"
+ - displayName: missing-secret-oauth
+ name: missing-secret-oauth
+ providerDisplayName: Kyma
+ description: Should return 500 when secret containing OAuth credentials is missing in the cluster
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-oauth"
+ credentials:
+ secretName: where-is-the-secret
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/server/oauth/token?client_secret=clientSecret"
+ type: OAuth
+ - displayName: missing-secret-basic-auth
+ name: missing-secret-basic-auth
+ providerDisplayName: Kyma
+ description: Should return 500 when secret containing Basic Auth credentials is missing in the cluster
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-basic-auth"
+ credentials:
+ secretName: where-is-the-secret
+ type: Basic
+ - displayName: missing-secret-oauth-mtls
+ name: missing-secret-oauth-mtls
+ providerDisplayName: Kyma
+ description: Should return 500 when secret containing OAuth mTLS credentials is missing in the cluster
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-oauth-mtls"
+ credentials:
+ secretName: where-is-the-secret
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/server/oauth/token?client_secret=clientSecret"
+ type: OAuthWithCert
+ - displayName: missing-secret-certgen-mtls
+ name: missing-secret-certgen-mtls
+ providerDisplayName: Kyma
+ description: Should return 500 when secret containing Cert Gen mTLS credentials is missing in the cluster
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-secret-certgen-mtls"
+ credentials:
+ secretName: where-is-the-secret
+ type: CertificateGen
+ - displayName: missing-request-parameters-header
+ name: missing-request-parameters-header
+ providerDisplayName: Kyma
+ description: Should return 500 when secret and request parameters credentials is missing in the cluster
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/missing-request-parameters-header"
+ requestParametersSecretName: where-are-the-paramterers
+ credentials:
+ secretName: basic-test
+ type: Basic
+ - displayName: non-existing-target-url
+ name: non-existing-target-url
+ providerDisplayName: Kyma
+ description: Should return 502 when target url is not resolvable
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://bad.bad.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/missing-resources-error-handling/non-existing-target-url"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml
new file mode 100644
index 00000000..79329289
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/negative-authorisation.yml
@@ -0,0 +1,254 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: negative-authorisation
+ namespace: {{ .Values.global.namespace }}
+spec:
+ description: Negative authorisation
+ skipVerify: true
+ labels:
+ app: negative-authorisation
+ services:
+ - displayName: bad oauth token
+ name: bad-oauth-token
+ providerDisplayName: OAuth
+ description: Should return 401 for OAuth with a wrong token
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-oauth-token"
+ credentials:
+ secretName: oauth-test-negative-case
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/bad-token"
+ type: OAuth
+ - displayName: wrong oauth secret
+ name: wrong-oauth-secret
+ providerDisplayName: OAuth
+ description: Should return 502 for OAuth with a wrong secret
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/wrong-oauth-secret"
+ credentials:
+ secretName: wrong-oauth-test
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token"
+ type: OAuth
+ - displayName: mtls-oauth-other-ca
+ name: mtls-oauth-other-ca
+ providerDisplayName: mTLS-OAuth
+ description: Should return 500 for mTLS Oauth with client certificate generated from other CA
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-other-ca"
+ credentials:
+ secretName: mtls-oauth-negative-other-ca
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ - displayName: mtls-oauth-incorrect-clientid
+ name: mtls-oauth-incorrect-clientid
+ providerDisplayName: mTLS-OAuth
+ description: Should return 500 for mTLS Oauth with valid certificate but invalid client id
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-incorrect-clientid"
+ credentials:
+ secretName: mtls-oauth-negative-incorrect-clientid
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ - displayName: mtls-oauth-negative-expired-client-cert
+ name: mtls-oauth-negative-expired-client-cert
+ providerDisplayName: mTLS-OAuth
+ description: Should return 500 for mTLS Oauth with expired client certificate
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-negative-expired-client-cert"
+ credentials:
+ secretName: mtls-oauth-negative-expired-client-cert
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ - displayName: mtls-oauth-negative-expired-server-cert
+ name: mtls-oauth-negative-expired-server-cert
+ providerDisplayName: mTLS-OAuth
+ description: Should return 500 for mTLS Oauth with expired server certificate
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-oauth-negative-expired-server-cert"
+ credentials:
+ secretName: mtls-oauth-negative-expired-server-cert
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8091/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ - displayName: mtls-negative-other-ca
+ name: mtls-negative-other-ca
+ providerDisplayName: mTLS
+ description: Should return 502 for mTLS with client certificate generated from other CA
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-other-ca"
+ credentials:
+ secretName: mtls-negative-other-ca
+ type: CertificateGen
+ - displayName: mtls-negative-expired-client-cert
+ name: mtls-negative-expired-client-cert
+ providerDisplayName: mTLS
+ description: Should return 502 for mTLS with expired client certificate
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-expired-client-cert"
+ credentials:
+ secretName: mtls-negative-expired-client-cert
+ type: CertificateGen
+ - displayName: mtls-negative-expired-server-cert
+ name: mtls-negative-expired-server-cert
+ providerDisplayName: mTLS
+ description: Should return 502 for mTLS with expired server certificate
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8091/v1/api/mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/mtls-negative-expired-server-cert"
+ credentials:
+ secretName: mtls-negative-expired-client-cert
+ type: CertificateGen
+ - displayName: bad csrf token basic
+ name: bad-csrf-token-basic
+ providerDisplayName: Basic with CSRF
+ description: Should return 403 for Basic Auth with a bad CSRF token
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-basic"
+ credentials:
+ secretName: basic-test-negative-case
+ type: Basic
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token"
+ - displayName: bad csrf endpoint basic
+ name: bad-csrf-endpoint-basic
+ providerDisplayName: Basic with CSRF
+ description: Should return 502 for Basic Auth with a bad CSRF token endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-basic"
+ credentials:
+ secretName: basic-test-negative-case
+ type: Basic
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath"
+ - displayName: bad csrf token oauth
+ name: bad-csrf-token-oauth
+ providerDisplayName: OAuth with CSRF
+ description: Should return 403 for OAuth with a bad CSRF token
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-oauth"
+ credentials:
+ secretName: oauth-test-negative-case
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token"
+ type: OAuth
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token"
+ - displayName: bad csrf endpoint oauth
+ name: bad-csrf-endpoint-oauth
+ providerDisplayName: OAuth with CSRF
+ description: Should return 502 for OAuth with a bad CSRF token endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-oauth"
+ credentials:
+ secretName: oauth-test-negative-case
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token"
+ type: OAuth
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath"
+ - displayName: bad csrf token mtls oauth
+ name: bad-csrf-token-mtls-oauth
+ providerDisplayName: mTLS-OAuth with CSRF
+ description: Should return 403 for mTLS OAuth with a bad CSRF token
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-mtls-oauth"
+ credentials:
+ secretName: mtls-oauth-negative-case
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token"
+ - displayName: bad csrf endpoint mtls oauth
+ name: bad-csrf-endpoint-mtls-oauth
+ providerDisplayName: mTLS-OAuth with CSRF
+ description: Should return 502 for mTLS OAuth with a bad CSRF token endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-mtls-oauth"
+ credentials:
+ secretName: mtls-oauth-negative-case
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath"
+ - displayName: bad csrf token mtls
+ name: bad-csrf-token-mtls
+ providerDisplayName: mTLS with CSRF
+ description: Should return 403 for mTLS with a bad CSRF token
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-token-mtls"
+ credentials:
+ secretName: mtls-negative-case
+ type: CertificateGen
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/bad-token"
+ - displayName: bad csrf endpoint mtls
+ name: bad-csrf-endpoint-mtls
+ providerDisplayName: mTLS with CSRF
+ description: Should return 502 for mTLS with a bad CSRF token endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/bad-csrf-endpoint-mtls"
+ credentials:
+ secretName: mtls-negative-case
+ type: CertificateGen
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/nonexistingpath"
+ - displayName: basic-auth-with-wrong-request-parameters
+ name: basic-auth-with-wrong-request-parameters
+ providerDisplayName: Basic
+ description: Should return 400 when calling endpoint protected with Basic Auth with wrong additional request parameters
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/request-parameters-basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/negative-authorisation/basic-auth-with-wrong-request-parameters"
+ requestParametersSecretName: wrong-request-parameters-test
+ credentials:
+ secretName: basic-test
+ type: Basic
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml
new file mode 100644
index 00000000..acdcca70
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/path-related-error-handling.yml
@@ -0,0 +1,29 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: path-related-error-handling
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Path handling
+ skipVerify: true
+ labels:
+ app: path-related-error-handling
+ services:
+ - displayName: missing-srv-app
+ name: missing-srv-app
+ providerDisplayName: Kyma
+ description: Should return 400 when service and application are missing in the path
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080"
+ - displayName: missing-srv
+ name: missing-srv
+ providerDisplayName: Kyma
+ description: Should return 400 when service is missing in the path
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/path-related-error-handling"
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml
new file mode 100644
index 00000000..e22ca854
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/positive-authorisation.yml
@@ -0,0 +1,141 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: positive-authorisation
+ namespace: {{ .Values.global.namespace }}
+spec:
+ description: Authorisation
+ skipVerify: true
+ labels:
+ app: positive-authorisation
+ services:
+ - displayName: unsecure-always-ok
+ name: unsecure-always-ok
+ providerDisplayName: AlwaysOK
+ description: Should return 200 when calling unprotected endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/unsecure-always-ok"
+ - displayName: basic-auth-ok
+ name: basic-auth-ok
+ providerDisplayName: Basic
+ description: Should return 200 when calling endpoint protected with Basic Auth
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/basic-auth-ok"
+ credentials:
+ secretName: basic-test
+ type: Basic
+ - displayName: oauth
+ name: oauth
+ providerDisplayName: OAuth
+ description: Should return 200 when calling endpoint protected with OAuth
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/oauth"
+ credentials:
+ secretName: oauth-test
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token"
+ type: OAuth
+ - displayName: mtls-oauth
+ name: mtls-oauth
+ providerDisplayName: mTLS-OAuth
+ description: Should return 200 when calling endpoint protected with mTLS OAuth
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/mtls-oauth"
+ credentials:
+ secretName: mtls-oauth-positive
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ - displayName: mtls
+ name: mtls
+ providerDisplayName: mTLS
+ description: Should return 200 when calling endpoint protected with mTLS
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/mtls"
+ credentials:
+ secretName: mtls-positive
+ type: CertificateGen
+ - displayName: csrf basic
+ name: csrf-basic
+ providerDisplayName: Basic with CSRF
+ description: Should return 200 for Basic Auth with CSRF optimistic scenario
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-basic"
+ credentials:
+ secretName: basic-test
+ type: Basic
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token"
+ - displayName: csrf-oauth
+ name: csrf-oauth
+ providerDisplayName: OAuth with CSRF
+ description: Should return 200 when calling endpoint protected with OAuth with CSRF
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-oauth"
+ credentials:
+ secretName: oauth-test
+ authenticationUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/oauth/token"
+ type: OAuth
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token"
+ - displayName: csrf-mtls-oauth
+ name: csrf-mtls-oauth
+ providerDisplayName: mTLS-OAuth with CSRF
+ description: Should return 200 when calling endpoint protected with mTLS OAuth with CSRF
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf-oauth/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-mtls-oauth"
+ credentials:
+ secretName: mtls-oauth-positive
+ authenticationUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/mtls-oauth/token"
+ type: OAuthWithCert
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token"
+ - displayName: csrf-mtls
+ name: csrf-mtls
+ providerDisplayName: mTLS with CSRF
+ description: Should return 200 when calling endpoint protected with mTLS with CSRF
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "https://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8090/v1/api/csrf-mtls/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/csrf-mtls"
+ credentials:
+ secretName: mtls-positive
+ type: CertificateGen
+ csrfInfo:
+ tokenEndpointURL: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/csrf/token"
+ - displayName: basic-auth-with-request-parameters
+ name: basic-auth-with-request-parameters
+ providerDisplayName: Basic
+ description: Should return 200 when calling endpoint protected with Basic Auth with additional request parameters
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/request-parameters-basic/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/positive-authorisation/basic-auth-with-request-parameters"
+ requestParametersSecretName: request-parameters-test
+ credentials:
+ secretName: basic-test
+ type: Basic
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml
new file mode 100644
index 00000000..54b558e1
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-cases.yml
@@ -0,0 +1,38 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: proxy-cases
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Proxying
+ skipVerify: true
+ labels:
+ app: proxy-cases
+ services:
+ - displayName: code 451
+ name: code 451
+ providerDisplayName: code 451
+ description: Should return 451 forwarded from target endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/451"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-451"
+ - displayName: code 307
+ name: code 307
+ providerDisplayName: code 307
+ description: Should return 307 forwarded from target endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/307"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-307"
+ - displayName: code 203
+ name: code 203
+ providerDisplayName: code 203
+ description: Should return 203 forwarded from target endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/code/203"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-cases/code-203"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml
new file mode 100644
index 00000000..c0b46d34
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/proxy-errors.yml
@@ -0,0 +1,20 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: proxy-errors
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Proxying edge cases
+ skipVerify: true
+ labels:
+ app: proxy-errors
+ services:
+ - displayName: timeout
+ name: timeout
+ providerDisplayName: timeout
+ description: Should return 504 when target times out
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.global.mockServiceName }}.{{ .Values.global.namespace }}.svc.cluster.local:8080/v1/api/unsecure/timeout"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/proxy-errors/timeout"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml b/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml
new file mode 100644
index 00000000..40d5137d
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/applications/redirect.yml
@@ -0,0 +1,41 @@
+apiVersion: applicationconnector.kyma-project.io/v1alpha1
+kind: Application
+metadata:
+ name: redirects
+ namespace: "{{ .Values.global.namespace }}"
+spec:
+ description: Endpoints for redirect cases
+ skipVerify: true
+ labels:
+ app: redirect-cases
+ services:
+ - displayName: unsecured
+ name: unsecured
+ providerDisplayName: unsecured
+ description: Should return 200 when redirected to unsecured endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/ok"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/unsecured"
+ - displayName: basic
+ name: basic
+ providerDisplayName: basic
+ description: Should return 200 when redirected to basic-auth endpoint
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/basic"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/basic"
+ credentials:
+ secretName: redirect-basic
+ type: Basic
+ - displayName: external
+ name: external
+ providerDisplayName: external
+ description: Should return 200 when redirected to external service
+ id: "{{ uuidv4 }}"
+ entries:
+ - type: API
+ targetUrl: "http://{{ .Values.mockServiceName }}.{{ .Values.namespace }}.svc.cluster.local:8080/v1/api/redirect/external"
+ centralGatewayUrl: "http://central-application-gateway.kyma-system:8080/redirects/external"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml b/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml
new file mode 100644
index 00000000..80fd71f1
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/service-account.yml
@@ -0,0 +1,33 @@
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+ name: {{ .Values.global.serviceAccountName }}
+ namespace: {{ .Values.global.namespace }}
+automountServiceAccountToken: true
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+ name: {{ .Values.global.serviceAccountName }}
+subjects:
+ - kind: ServiceAccount
+ name: {{ .Values.global.serviceAccountName }}
+ namespace: {{ .Values.global.namespace }}
+roleRef:
+ apiGroup: rbac.authorization.k8s.io
+ kind: ClusterRole
+ name: {{ .Values.global.serviceAccountName }}
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+ name: {{ .Values.global.serviceAccountName }}
+rules:
+ - verbs:
+ - get
+ - list
+ apiGroups:
+ - ""
+ - applicationconnector.kyma-project.io
+ resources:
+ - "*"
diff --git a/tests/resources/charts/gateway-test/charts/test/templates/test.yml b/tests/resources/charts/gateway-test/charts/test/templates/test.yml
new file mode 100644
index 00000000..b31e0115
--- /dev/null
+++ b/tests/resources/charts/gateway-test/charts/test/templates/test.yml
@@ -0,0 +1,15 @@
+apiVersion: batch/v1
+kind: Job
+metadata:
+ name: application-gateway-test
+ namespace: {{ .Values.global.namespace }}
+spec:
+ template:
+ spec:
+ containers:
+ - name: application-gateway-test
+ image: {{ include "imageurl" (dict "reg" .Values.global.containerRegistry "img" .Values.global.images.gatewayTest) }}
+ imagePullPolicy: Always
+ restartPolicy: Never
+ serviceAccountName: {{ .Values.global.serviceAccountName }}
+ backoffLimit: 0
\ No newline at end of file
diff --git a/tests/resources/charts/gateway-test/values.yaml b/tests/resources/charts/gateway-test/values.yaml
new file mode 100644
index 00000000..63ec00cb
--- /dev/null
+++ b/tests/resources/charts/gateway-test/values.yaml
@@ -0,0 +1,19 @@
+global:
+ containerRegistry:
+ path: "europe-docker.pkg.dev/kyma-project"
+
+ images:
+ gatewayTest:
+ name: "gateway-test"
+ version: "v20230925-75c3a9a8"
+ directory: "prod"
+ mockApplication:
+ name: "mock-app"
+ version: "v20230925-75c3a9a8"
+ directory: "prod"
+
+ serviceAccountName: "test-account"
+ namespace: "test"
+
+ mockServiceName: "mock-application"
+
diff --git a/tests/resources/installation-config/mini-kyma-os.yaml b/tests/resources/installation-config/mini-kyma-os.yaml
new file mode 100644
index 00000000..63eb2f7c
--- /dev/null
+++ b/tests/resources/installation-config/mini-kyma-os.yaml
@@ -0,0 +1,9 @@
+---
+defaultNamespace: kyma-system
+prerequisites:
+ - name: "istio"
+ namespace: "istio-system"
+ - name: "certificates"
+ namespace: "istio-system"
+components:
+ - name: "application-connector"
\ No newline at end of file
diff --git a/tests/resources/installation-config/mini-kyma-skr.yaml b/tests/resources/installation-config/mini-kyma-skr.yaml
new file mode 100644
index 00000000..d1a6e717
--- /dev/null
+++ b/tests/resources/installation-config/mini-kyma-skr.yaml
@@ -0,0 +1,10 @@
+---
+defaultNamespace: kyma-system
+prerequisites:
+ - name: "istio"
+ namespace: "istio-system"
+ - name: "certificates"
+ namespace: "istio-system"
+components:
+ - name: "application-connector"
+ - name: "compass-runtime-agent"
\ No newline at end of file
diff --git a/tests/resources/patches/central-application-connectivity-validator.json b/tests/resources/patches/central-application-connectivity-validator.json
new file mode 100644
index 00000000..b676d8dd
--- /dev/null
+++ b/tests/resources/patches/central-application-connectivity-validator.json
@@ -0,0 +1,24 @@
+[
+ {
+ "op": "replace",
+ "path": "/spec/template/spec/containers/0/args",
+ "value": [
+ "/app/centralapplicationconnectivityvalidator",
+ "--proxyPort=8080",
+ "--externalAPIPort=8081",
+ "--eventingPathPrefixV1=/%%APP_NAME%%/v1/events",
+ "--eventingPathPrefixV2=/%%APP_NAME%%/v2/events",
+ "--eventingPublisherHost=echoserver.test.svc.cluster.local",
+ "--eventingDestinationPath=/anything/rewrite",
+ "--eventingPathPrefixEvents=/%%APP_NAME%%/events",
+ "--appNamePlaceholder=%%APP_NAME%%",
+ ]
+ },
+ {
+ "op": "add",
+ "path": "/spec/template/metadata/annotations",
+ "value": {
+ "traffic.sidecar.istio.io/excludeInboundPorts": "8080"
+ }
+ }
+]
diff --git a/tests/resources/patches/coredns.yaml b/tests/resources/patches/coredns.yaml
new file mode 100644
index 00000000..1a7c466f
--- /dev/null
+++ b/tests/resources/patches/coredns.yaml
@@ -0,0 +1,40 @@
+apiVersion: v1
+data:
+ Corefile: |2
+
+ .:53 {
+ errors
+ health
+ rewrite name regex (.*)\.local\.kyma\.dev istio-ingressgateway.istio-system.svc.cluster.local
+ ready
+ kubernetes cluster.local in-addr.arpa ip6.arpa {
+ pods insecure
+ fallthrough in-addr.arpa ip6.arpa
+ }
+ hosts /etc/coredns/NodeHosts {
+ reload 1s
+ fallthrough
+ }
+ prometheus :9153
+ forward . tls://1.1.1.1 tls://1.0.0.1 {
+ tls_servername cloudflare-dns.com
+ health_check 5s
+ }
+ cache 30
+ loop
+ reload
+ loadbalance
+ }
+
+ NodeHosts: |
+ 172.18.0.3 k3d-kyma-server-0
+ 172.18.0.2 k3d-kyma-registry
+ 172.18.0.4 k3d-kyma-agent-0
+kind: ConfigMap
+metadata:
+ annotations:
+ objectset.rio.cattle.io/owner-gvk: k3s.cattle.io/v1, Kind=Addon
+ objectset.rio.cattle.io/owner-name: coredns
+ objectset.rio.cattle.io/owner-namespace: kube-system
+ name: coredns
+ namespace: kube-system
diff --git a/tests/scripts/fetch-test-logs.sh b/tests/scripts/fetch-test-logs.sh
new file mode 100755
index 00000000..3420b3da
--- /dev/null
+++ b/tests/scripts/fetch-test-logs.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+# JOB_NAME - the name of the job that is runing tests (required)
+# LOGS_OUT - path to directory where the logs will be stored (optional, default: ${PWD})
+# NAMESPACE the - namespace where the test job is runing (optional,default: test)
+# TEST_TIMEOUT - duration to wait on tests to finish (optional, default: 900s)
+
+function fetch_tests() {
+ local JOB_NAME=$1
+ # check if JOB_NAME is provided
+ if [ -z "$JOB_NAME" ]; then
+ echo "Usage: $0 [LOGS_OUT] [NAMESPACE] [TEST_TIMEOUT]"
+ exit 1
+ fi
+ local LOGS_OUT=${2:-${PWD}}
+ local NAMESPACE=${3:-test}
+ local TEST_TIMEOUT=${4:-300s}
+ # wait for the job to finish
+ kubectl wait job/$JOB_NAME \
+ -n $NAMESPACE \
+ --for=condition=complete \
+ --timeout=$TEST_TIMEOUT
+ # store the exit code of the job
+ local __job_result__=$?
+ # try to get the logs of the job and store them in the TEST_LOG file
+ kubectl logs \
+ -n $NAMESPACE \
+ -f job/$JOB_NAME \
+ 2>&1 > "$LOGS_OUT"/$JOB_NAME.log
+ # exit with original job exit code
+ exit $__job_result__
+}
+
+fetch_tests $@
diff --git a/tests/scripts/generate-self-signed-certs.sh b/tests/scripts/generate-self-signed-certs.sh
new file mode 100755
index 00000000..9cd6422e
--- /dev/null
+++ b/tests/scripts/generate-self-signed-certs.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+if [ $# -ne 2 ]; then
+ echo "Usage: generate-self-signed-certs.sh "
+ exit 1
+fi
+
+export APP_URL=$1
+export GATEWAY_TEST_CERTS_DIR=$2
+export SUBJECT="/C=PL/ST=A/O=SAP/CN=$APP_URL"
+
+mkdir -p "$GATEWAY_TEST_CERTS_DIR"
+
+echo "Generating certificate for domain: $APP_URL"
+openssl version
+openssl req -newkey rsa:2048 -nodes -x509 -days 365 -out "$GATEWAY_TEST_CERTS_DIR/ca.crt" -keyout "$GATEWAY_TEST_CERTS_DIR/ca.key" -subj $SUBJECT
+
+openssl genrsa -out "$GATEWAY_TEST_CERTS_DIR/server.key" 2048
+openssl genrsa -out "$GATEWAY_TEST_CERTS_DIR"/client.key 2048
+
+openssl req -new \
+ -key "$GATEWAY_TEST_CERTS_DIR/server.key" \
+ -subj "$SUBJECT" \
+ -reqexts SAN \
+ -config <(cat /etc/ssl/openssl.cnf \
+ <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \
+ -out "$GATEWAY_TEST_CERTS_DIR/server.csr"
+
+openssl x509 -req -sha256 -days 365 -CA "$GATEWAY_TEST_CERTS_DIR/ca.crt" -CAkey "$GATEWAY_TEST_CERTS_DIR/ca.key" -CAcreateserial \
+ -extensions SAN \
+ -extfile <(cat /etc/ssl/openssl.cnf \
+ <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL" )) \
+ -in "$GATEWAY_TEST_CERTS_DIR/server.csr" -out "$GATEWAY_TEST_CERTS_DIR/server.crt"
+
+openssl req -new \
+ -key "$GATEWAY_TEST_CERTS_DIR/client.key" \
+ -subj "$SUBJECT" \
+ -reqexts SAN \
+ -config <(cat /etc/ssl/openssl.cnf \
+ <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \
+ -out "$GATEWAY_TEST_CERTS_DIR/client.csr"
+
+openssl x509 -req -sha256 -days 365 -CA "$GATEWAY_TEST_CERTS_DIR/ca.crt" -CAkey "$GATEWAY_TEST_CERTS_DIR/ca.key" -CAcreateserial \
+ -extensions SAN \
+ -extfile <(cat /etc/ssl/openssl.cnf \
+ <(printf "\n[SAN]\nsubjectAltName=DNS:%s" "$APP_URL")) \
+ -in "$GATEWAY_TEST_CERTS_DIR/client.csr" -out "$GATEWAY_TEST_CERTS_DIR/client.crt"
\ No newline at end of file
diff --git a/tests/scripts/jobguard.sh b/tests/scripts/jobguard.sh
new file mode 100755
index 00000000..f92a9653
--- /dev/null
+++ b/tests/scripts/jobguard.sh
@@ -0,0 +1,34 @@
+#!/bin/bash
+export GO111MODULE=on
+
+ROOT_PATH=$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")
+
+KYMA_PROJECT_DIR=${KYMA_PROJECT_DIR:-"/home/prow/go/src/github.com/kyma-project"}
+JOB_NAME_PATTERN=${JOB_NAME_PATTERN:-"(pre-main-kyma-components-.*)|(pre-main-kyma-tests-.*)|(pre-kyma-components-.*)|(pre-kyma-tests-.*)|(pull-.*-build)"}
+TIMEOUT=${JOBGUARD_TIMEOUT:-"15m"}
+
+export TEST_INFRA_SOURCES_DIR="${KYMA_PROJECT_DIR}/test-infra"
+
+if [ -z "$PULL_PULL_SHA" ]; then
+ echo "WORKAROUND: skip jobguard execution - not on PR commit"
+ exit 0
+fi
+
+args=(
+ "-github-endpoint=http://ghproxy"
+ "-github-endpoint=https://api.github.com"
+ "-github-token-path=/etc/github/token"
+ "-fail-on-no-contexts=false"
+ "-timeout=$TIMEOUT"
+ "-org=$REPO_OWNER"
+ "-repo=$REPO_NAME"
+ "-base-ref=$PULL_PULL_SHA"
+ "-expected-contexts-regexp=$JOB_NAME_PATTERN"
+)
+
+if [ -x "/prow-tools/jobguard" ]; then
+ /prow-tools/jobguard "${args[@]}"
+else
+ cd "${ROOT_PATH}/cmd/jobguard" || exit 1
+ go run main.go "${args[@]}"
+fi
diff --git a/tests/scripts/local-build.sh b/tests/scripts/local-build.sh
new file mode 100755
index 00000000..bb230665
--- /dev/null
+++ b/tests/scripts/local-build.sh
@@ -0,0 +1,10 @@
+#!/usr/bin/env bash
+
+if [ $# -ne 2 ]; then
+ echo "Usage: local_build.sh "
+ exit 1
+fi
+
+export DOCKER_TAG=$1
+export DOCKER_PUSH_REPOSITORY=$2
+make release
diff --git a/tests/scripts/test-cra.sh b/tests/scripts/test-cra.sh
new file mode 100755
index 00000000..f3313d10
--- /dev/null
+++ b/tests/scripts/test-cra.sh
@@ -0,0 +1,21 @@
+#!/usr/bin/env bash
+
+./tests/components/application-connector/scripts/jobguard.sh
+
+service docker start
+curl -s https://raw.githubusercontent.com/k3d-io/k3d/main/install.sh | bash
+curl -Lo kyma.tar.gz "https://github.com/kyma-project/cli/releases/download/$(curl -s https://api.github.com/repos/kyma-project/cli/releases/latest | grep tag_name | cut -d '"' -f 4)/kyma_Linux_x86_64.tar.gz" && mkdir kyma-release && tar -C kyma-release -zxvf kyma.tar.gz && chmod +x kyma-release/kyma && rm -rf kyma.tar.gz
+kyma-release/kyma provision k3d
+kubectl cluster-info
+kyma-release/kyma deploy --ci --components-file tests/components/application-connector/resources/installation-config/mini-kyma-skr.yaml --source local --workspace $PWD
+cd tests/components/application-connector
+
+# reconfigure DNS
+kubectl apply -f resources/patches/coredns.yaml
+kubectl -n kube-system delete pods -l k8s-app=kube-dns
+
+make -f Makefile.test-compass-runtime-agent test-compass-runtime-agent
+failed=$?
+
+k3d cluster delete kyma
+exit $failed
diff --git a/tests/test/application-connectivity-validator/suite_test.go b/tests/test/application-connectivity-validator/suite_test.go
new file mode 100644
index 00000000..b08c4fde
--- /dev/null
+++ b/tests/test/application-connectivity-validator/suite_test.go
@@ -0,0 +1,136 @@
+package application_connectivity_validator
+
+import (
+ "fmt"
+ "net/http"
+ "testing"
+
+ "github.com/stretchr/testify/suite"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd"
+)
+
+const v1EventsFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/v1/events"
+const v2EventsFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/v2/events"
+const publishRoutedFormat = "http://central-application-connectivity-validator.kyma-system:8080/%s/events"
+
+const XForwardedClientCertFormat = "Hash=hash1;Cert=\"cert\";Subject=\"O=client organization,CN=%s\";URI=,By=spiffe://cluster.local/ns/default/sa/echoserver;Hash=hash;Subject=\"\";URI=spiffe://cluster.local/ns/istio-system/sa/istio-ingressgateway-service-account"
+
+const standaloneAppName = "event-test-standalone"
+const compassAppName = "event-test-compass"
+
+type ValidatorSuite struct {
+ suite.Suite
+}
+
+func (vs *ValidatorSuite) SetupSuite() {
+}
+
+func (vs *ValidatorSuite) TearDownSuite() {
+ _, err := http.Post("http://localhost:15000/quitquitquit", "", nil)
+ vs.Nil(err)
+ _, err = http.Post("http://localhost:15020/quitquitquit", "", nil)
+ vs.Nil(err)
+}
+
+func TestValidatorSuite(t *testing.T) {
+ suite.Run(t, new(ValidatorSuite))
+}
+
+func (vs *ValidatorSuite) TestGoodCert() {
+ cli := httpd.NewCli(vs.T())
+
+ for _, testCase := range []struct {
+ appName string
+ expectedCName string
+ }{{
+ appName: standaloneAppName,
+ expectedCName: standaloneAppName,
+ }, {
+ appName: compassAppName,
+ expectedCName: "clientId1",
+ }} {
+ v1Events := fmt.Sprintf(v1EventsFormat, testCase.appName)
+ v2Events := fmt.Sprintf(v2EventsFormat, testCase.appName)
+ routedEvents := fmt.Sprintf(publishRoutedFormat, testCase.appName)
+ endpoints := []string{v1Events, v2Events, routedEvents}
+
+ for _, url := range endpoints {
+ vs.Run(fmt.Sprintf("Send request to %s URL", url), func() {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ vs.Nil(err)
+
+ req.Header.Add("X-Forwarded-Client-Cert", certFields(testCase.expectedCName))
+
+ res, _, err := cli.Do(req)
+ vs.Require().Nil(err)
+ vs.Equal(http.StatusOK, res.StatusCode)
+ })
+ }
+ }
+}
+
+func (vs *ValidatorSuite) TestBadCert() {
+ cli := httpd.NewCli(vs.T())
+
+ appNames := []string{standaloneAppName, compassAppName}
+
+ for _, appName := range appNames {
+ v1Events := fmt.Sprintf(v1EventsFormat, appName)
+ v2Events := fmt.Sprintf(v2EventsFormat, appName)
+ routedEvents := fmt.Sprintf(publishRoutedFormat, appName)
+ endpoints := []string{v1Events, v2Events, routedEvents}
+
+ for _, url := range endpoints {
+ vs.Run(fmt.Sprintf("Send request to %s URL with incorrect cname in header", url), func() {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ vs.Nil(err)
+
+ req.Header.Add("X-Forwarded-Client-Cert", certFields("nonexistant"))
+
+ res, _, err := cli.Do(req)
+ vs.Require().Nil(err)
+ vs.Equal(http.StatusForbidden, res.StatusCode)
+ })
+
+ vs.Run(fmt.Sprintf("Send request to %s URL without subject in header", url), func() {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ vs.Nil(err)
+
+ req.Header.Add("X-Forwarded-Client-Cert", "Hash=hash1;Cert=\"cert\"")
+
+ res, _, err := cli.Do(req)
+ vs.Require().Nil(err)
+ vs.Equal(http.StatusForbidden, res.StatusCode)
+ })
+
+ vs.Run(fmt.Sprintf("Send request to %s URL without header", url), func() {
+ req, err := http.NewRequest(http.MethodGet, url, nil)
+ vs.Nil(err)
+
+ res, _, err := cli.Do(req)
+ vs.Require().Nil(err)
+ vs.Equal(http.StatusInternalServerError, res.StatusCode)
+ })
+ }
+ }
+}
+
+func (vs *ValidatorSuite) TestInvalidPathPrefix() {
+ const v3vents = "http://central-application-connectivity-validator.kyma-system:8080/event-test-compass/v3/events"
+
+ cli := httpd.NewCli(vs.T())
+
+ req, err := http.NewRequest(http.MethodGet, v3vents, nil)
+ vs.Nil(err)
+
+ req.Header.Add("X-Forwarded-Client-Cert", certFields("clientId1"))
+
+ res, _, err := cli.Do(req)
+ vs.Require().Nil(err)
+ vs.Equal(http.StatusNotFound, res.StatusCode)
+}
+
+func certFields(cname string) string {
+ return fmt.Sprintf(XForwardedClientCertFormat, cname)
+}
diff --git a/tests/test/application-connectivity-validator/tools.go b/tests/test/application-connectivity-validator/tools.go
new file mode 100644
index 00000000..0d136f35
--- /dev/null
+++ b/tests/test/application-connectivity-validator/tools.go
@@ -0,0 +1,5 @@
+package application_connectivity_validator
+
+func validatorURL(app, path string) string {
+ return "http://central-application-connectivity-validator.kyma-system:8080/" + app + "/" + path
+}
diff --git a/tests/test/application-gateway/complex_test.go b/tests/test/application-gateway/complex_test.go
new file mode 100644
index 00000000..7d94b0a4
--- /dev/null
+++ b/tests/test/application-gateway/complex_test.go
@@ -0,0 +1,28 @@
+package application_gateway
+
+import (
+ "time"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd"
+)
+
+func (gs *GatewaySuite) TestComplex() {
+ gs.Run("OAuth token renewal", func() {
+ http := httpd.NewCli(gs.T())
+
+ url := gatewayURL("complex-cases", "oauth-expired-token-renewal")
+ gs.T().Log("Url:", url)
+
+ // Authorize, then call endpoint
+ res, _, err := http.Get(url)
+ gs.Nil(err, "First request failed")
+ gs.Equal(200, res.StatusCode, "First request failed")
+
+ time.Sleep(10 * time.Second) // wait for token to expire
+
+ // Call endpoint, requiring token renewall
+ res, _, err = http.Get(url)
+ gs.Nil(err, "Second request failed")
+ gs.Equal(200, res.StatusCode, "Second request failed")
+ })
+}
diff --git a/tests/test/application-gateway/runner_test.go b/tests/test/application-gateway/runner_test.go
new file mode 100644
index 00000000..76e4860c
--- /dev/null
+++ b/tests/test/application-gateway/runner_test.go
@@ -0,0 +1,109 @@
+package application_gateway
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+ "strings"
+
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/httpd"
+)
+
+var applications = []string{"positive-authorisation", "negative-authorisation", "path-related-error-handling", "missing-resources-error-handling", "proxy-cases", "proxy-errors", "redirects", "code-rewriting"}
+
+func (gs *GatewaySuite) TestGetRequest() {
+
+ for _, app := range applications {
+ app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), app, v1.GetOptions{})
+ gs.Nil(err)
+
+ gs.Run(app.Spec.Description, func() {
+ for _, service := range app.Spec.Services {
+ gs.Run(service.Description, func() {
+ http := httpd.NewCli(gs.T())
+
+ for _, entry := range service.Entries {
+ if entry.Type != "API" {
+ gs.T().Log("Skipping event entry")
+ continue
+ }
+
+ expectedCode, err := getExpectedHTTPCode(service)
+ if err != nil {
+ gs.T().Log("Error during getting the error code from description -> applicationCRD")
+ gs.T().Fail()
+ }
+
+ res, _, err := http.Get(entry.CentralGatewayUrl)
+ gs.Nil(err, "Request failed")
+ gs.Equal(expectedCode, res.StatusCode, "Incorrect response code")
+ }
+ })
+ }
+ })
+ }
+}
+
+func (gs *GatewaySuite) TestResponseBody() {
+ app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), "proxy-cases", v1.GetOptions{})
+ gs.Nil(err)
+ for _, service := range app.Spec.Services {
+ gs.Run(service.Description, func() {
+ http := httpd.NewCli(gs.T())
+
+ for _, entry := range service.Entries {
+ if entry.Type != "API" {
+ gs.T().Log("Skipping event entry")
+ continue
+ }
+
+ expectedCode, err := getExpectedHTTPCode(service)
+ if err != nil {
+ gs.T().Log("Error during getting the error code from description -> applicationCRD")
+ gs.T().Fail()
+ }
+
+ _, body, err := http.Get(entry.CentralGatewayUrl)
+ gs.Nil(err, "Request failed")
+
+ codeStr := strconv.Itoa(expectedCode)
+
+ gs.Equal(codeStr, string(body), "Incorrect body")
+ }
+ })
+ }
+}
+
+func (gs *GatewaySuite) TestBodyPerMethod() {
+ app, err := gs.cli.ApplicationconnectorV1alpha1().Applications().Get(context.Background(), "methods-with-body", v1.GetOptions{})
+ gs.Nil(err)
+ for _, service := range app.Spec.Services {
+ gs.Run(service.Description, func() {
+ httpCli := httpd.NewCli(gs.T())
+
+ for _, entry := range service.Entries {
+ if entry.Type != "API" {
+ gs.T().Log("Skipping event entry")
+ continue
+ }
+
+ method := service.Description
+ bodyBuf := strings.NewReader(service.Description)
+
+ req, err := http.NewRequest(method, entry.CentralGatewayUrl, bodyBuf)
+ gs.Nil(err, "Preparing request failed")
+
+ _, body, err := httpCli.Do(req)
+ gs.Nil(err, "Request failed")
+
+ res, err := unmarshalBody(body)
+ gs.Nil(err, "Response body wasn't correctly forwarded")
+
+ gs.Equal(service.Description, string(res.Body), "Request body doesn't match")
+ gs.Equal(service.Description, res.Method, "Request method doesn't match")
+ }
+ })
+ }
+}
diff --git a/tests/test/application-gateway/suite_test.go b/tests/test/application-gateway/suite_test.go
new file mode 100644
index 00000000..764ebabf
--- /dev/null
+++ b/tests/test/application-gateway/suite_test.go
@@ -0,0 +1,34 @@
+package application_gateway
+
+import (
+ "net/http"
+ "testing"
+
+ cli "github.com/kyma-project/kyma/components/central-application-gateway/pkg/client/clientset/versioned"
+ "github.com/stretchr/testify/suite"
+ "k8s.io/client-go/rest"
+)
+
+type GatewaySuite struct {
+ suite.Suite
+ cli *cli.Clientset
+}
+
+func (gs *GatewaySuite) SetupSuite() {
+ cfg, err := rest.InClusterConfig()
+ gs.Require().Nil(err)
+
+ gs.cli, err = cli.NewForConfig(cfg)
+ gs.Require().Nil(err)
+}
+
+func (gs *GatewaySuite) TearDownSuite() {
+ _, err := http.Post("http://localhost:15000/quitquitquit", "", nil)
+ gs.Nil(err)
+ _, err = http.Post("http://localhost:15020/quitquitquit", "", nil)
+ gs.Nil(err)
+}
+
+func TestGatewaySuite(t *testing.T) {
+ suite.Run(t, new(GatewaySuite))
+}
diff --git a/tests/test/application-gateway/tools.go b/tests/test/application-gateway/tools.go
new file mode 100644
index 00000000..135c49c6
--- /dev/null
+++ b/tests/test/application-gateway/tools.go
@@ -0,0 +1,30 @@
+package application_gateway
+
+import (
+ "encoding/json"
+ "regexp"
+ "strconv"
+
+ "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1"
+ "github.com/pkg/errors"
+
+ test_api "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/test-api"
+)
+
+func getExpectedHTTPCode(service v1alpha1.Service) (int, error) {
+ re := regexp.MustCompile(`\d+`)
+ if codeStr := re.FindString(service.Description); len(codeStr) > 0 {
+ return strconv.Atoi(codeStr)
+ }
+ return 0, errors.New("Bad configuration")
+}
+
+func gatewayURL(app, service string) string {
+ return "http://central-application-gateway.kyma-system:8080/" + app + "/" + service
+}
+
+func unmarshalBody(body []byte) (test_api.EchoResponse, error) {
+ res := test_api.EchoResponse{}
+ err := json.Unmarshal(body, &res)
+ return res, err
+}
diff --git a/tests/test/compass-runtime-agent/config.go b/tests/test/compass-runtime-agent/config.go
new file mode 100644
index 00000000..c004c398
--- /dev/null
+++ b/tests/test/compass-runtime-agent/config.go
@@ -0,0 +1,19 @@
+package compass_runtime_agent
+
+import "fmt"
+
+type config struct {
+ DirectorURL string `envconfig:"default=http://compass-director.compass-system.svc.cluster.local:3000/graphql"`
+ SkipDirectorCertVerification bool `envconfig:"default=false"`
+ OAuthCredentialsNamespace string `envconfig:"default=test"`
+ SystemNamespace string `envconfig:"default=kyma-system"`
+ CompassRuntimeAgentDeploymentName string `envconfig:"default=compass-runtime-agent"`
+ CompassNamespace string `envconfig:"default=kyma-system"`
+ OAuthCredentialsSecretName string `envconfig:"default=oauth-compass-credentials"`
+ TestingTenant string `envconfig:"default=tenant"`
+}
+
+func (c *config) String() string {
+ return fmt.Sprintf("DirectorURL: %s, SkipDirectorCertVerification: %v, OAuthCredentialsNamespace: %s, IntegrationNamespace: %s, CompassNamespace: %s, OAuthCredentialsSecretName: %s, TestingTenant %s",
+ c.DirectorURL, c.SkipDirectorCertVerification, c.OAuthCredentialsNamespace, c.SystemNamespace, c.CompassNamespace, c.OAuthCredentialsSecretName, c.TestingTenant)
+}
diff --git a/tests/test/compass-runtime-agent/suite_test.go b/tests/test/compass-runtime-agent/suite_test.go
new file mode 100644
index 00000000..c3e62ea2
--- /dev/null
+++ b/tests/test/compass-runtime-agent/suite_test.go
@@ -0,0 +1,166 @@
+package compass_runtime_agent
+
+import (
+ "crypto/tls"
+ "fmt"
+ "net/http"
+ "os"
+ "testing"
+ "time"
+
+ cli "github.com/kyma-project/kyma/components/central-application-gateway/pkg/client/clientset/versioned"
+ ccclientset "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/client/clientset/versioned"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/suite"
+ "github.com/vrischmann/envconfig"
+ "k8s.io/client-go/kubernetes"
+ "k8s.io/client-go/rest"
+ "k8s.io/client-go/tools/clientcmd"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/applications"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/director"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql"
+ initcra "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init"
+ compassruntimeagentinittypes "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/random"
+)
+
+type CompassRuntimeAgentSuite struct {
+ suite.Suite
+ applicationsClientSet *cli.Clientset
+ compassConnectionClientSet *ccclientset.Clientset
+ coreClientSet *kubernetes.Clientset
+ compassRuntimeAgentConfigurator initcra.CompassRuntimeAgentConfigurator
+ directorClient director.Client
+ appComparator applications.Comparator
+ testConfig config
+ rollbackTestFunc compassruntimeagentinittypes.RollbackFunc
+ formationName string
+}
+
+func (cs *CompassRuntimeAgentSuite) SetupSuite() {
+
+ err := envconfig.InitWithPrefix(&cs.testConfig, "APP")
+ cs.Require().Nil(err)
+
+ cs.T().Logf("Config: %s", cs.testConfig.String())
+
+ cs.T().Logf("Init Kubernetes APIs")
+ cs.initKubernetesApis()
+
+ cs.T().Logf("Configure Compass Runtime Agent for test")
+ cs.initCompassRuntimeAgentConfigurator()
+ cs.initComparators()
+ cs.configureRuntimeAgent()
+}
+
+func (cs *CompassRuntimeAgentSuite) initKubernetesApis() {
+ var cfg *rest.Config
+ var err error
+
+ cs.T().Logf("Initializing with in cluster config")
+ cfg, err = rest.InClusterConfig()
+ cs.Assert().NoError(err)
+
+ if err != nil {
+ cs.T().Logf("Initializing kubeconfig")
+ kubeconfig, ok := os.LookupEnv("KUBECONFIG")
+ cs.Require().True(ok)
+
+ cfg, err = clientcmd.BuildConfigFromFlags("", kubeconfig)
+ cs.Require().NoError(err)
+ }
+
+ cs.applicationsClientSet, err = cli.NewForConfig(cfg)
+ cs.Require().NoError(err)
+
+ cs.compassConnectionClientSet, err = ccclientset.NewForConfig(cfg)
+ cs.Require().NoError(err)
+
+ cs.coreClientSet, err = kubernetes.NewForConfig(cfg)
+ cs.Require().NoError(err)
+}
+
+func (cs *CompassRuntimeAgentSuite) initComparators() {
+ secretComparator, err := applications.NewSecretComparator(cs.coreClientSet, cs.testConfig.OAuthCredentialsNamespace, cs.testConfig.SystemNamespace)
+ cs.Require().NoError(err)
+
+ applicationGetter := cs.applicationsClientSet.ApplicationconnectorV1alpha1().Applications()
+ cs.appComparator, err = applications.NewComparator(secretComparator, applicationGetter, "kyma-system", "kyma-system")
+}
+
+func (cs *CompassRuntimeAgentSuite) configureRuntimeAgent() {
+ cs.T().Helper()
+
+ var err error
+ runtimeName := "cratest"
+ cs.formationName = "cratest" + random.RandomString(5)
+
+ cs.rollbackTestFunc, err = cs.compassRuntimeAgentConfigurator.Do(runtimeName, cs.formationName)
+ cs.Require().NoError(err)
+}
+
+func (cs *CompassRuntimeAgentSuite) initCompassRuntimeAgentConfigurator() {
+ var err error
+ cs.directorClient, err = cs.makeCompassDirectorClient()
+ cs.Require().NoError(err)
+
+ cs.compassRuntimeAgentConfigurator = initcra.NewCompassRuntimeAgentConfigurator(
+ initcra.NewCompassConfigurator(cs.directorClient, cs.testConfig.TestingTenant),
+ initcra.NewCertificateSecretConfigurator(cs.coreClientSet),
+ initcra.NewConfigurationSecretConfigurator(cs.coreClientSet),
+ initcra.NewCompassConnectionCRConfiguration(cs.compassConnectionClientSet.CompassV1alpha1().CompassConnections()),
+ initcra.NewDeploymentConfiguration(cs.coreClientSet, "compass-runtime-agent", cs.testConfig.CompassNamespace),
+ cs.testConfig.OAuthCredentialsNamespace)
+}
+
+func (cs *CompassRuntimeAgentSuite) TearDownSuite() {
+ if cs.rollbackTestFunc != nil {
+ cs.T().Logf("Restore Compass Runtime Agent configuration")
+ err := cs.rollbackTestFunc()
+
+ if err != nil {
+ cs.T().Logf("Failed to rollback test configuration: %v", err)
+ }
+ }
+ _, err := http.Post("http://localhost:15000/quitquitquit", "", nil)
+ if err != nil {
+ cs.T().Logf("Failed to quit sidecar: %v", err)
+ }
+ _, err = http.Post("http://localhost:15020/quitquitquit", "", nil)
+ if err != nil {
+ cs.T().Logf("Failed to quit sidecar: %v", err)
+ }
+}
+
+func TestCompassRuntimeAgentSuite(t *testing.T) {
+ suite.Run(t, new(CompassRuntimeAgentSuite))
+}
+
+func (cs *CompassRuntimeAgentSuite) makeCompassDirectorClient() (director.Client, error) {
+
+ secretsRepo := cs.coreClientSet.CoreV1().Secrets(cs.testConfig.OAuthCredentialsNamespace)
+
+ if secretsRepo == nil {
+ return nil, fmt.Errorf("could not access secrets in %s namespace", cs.testConfig.OAuthCredentialsNamespace)
+ }
+ client := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: cs.testConfig.SkipDirectorCertVerification},
+ },
+ Timeout: 10 * time.Second,
+ }
+
+ gqlClient := graphql.NewGraphQLClient(cs.testConfig.DirectorURL, true, cs.testConfig.SkipDirectorCertVerification)
+ if gqlClient == nil {
+ return nil, fmt.Errorf("could not create GraphQLClient for endpoint %s", cs.testConfig.DirectorURL)
+ }
+
+ oauthClient, err := oauth.NewOauthClient(client, secretsRepo, cs.testConfig.OAuthCredentialsSecretName)
+ if err != nil {
+ return nil, errors.Wrap(err, "Could not create OAuthClient client")
+ }
+
+ return director.NewDirectorClient(gqlClient, oauthClient, cs.testConfig.TestingTenant), nil
+}
diff --git a/tests/test/compass-runtime-agent/synchronisation_test.go b/tests/test/compass-runtime-agent/synchronisation_test.go
new file mode 100644
index 00000000..ac19f018
--- /dev/null
+++ b/tests/test/compass-runtime-agent/synchronisation_test.go
@@ -0,0 +1,157 @@
+package compass_runtime_agent
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1"
+ "k8s.io/apimachinery/pkg/api/errors"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/executor"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/random"
+)
+
+const checkAppExistsPeriod = 10 * time.Second
+const appCreationTimeout = 2 * time.Minute
+const appUpdateTimeout = 2 * time.Minute
+
+const updatedDescription = "The app was updated"
+
+type ApplicationReader interface {
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error)
+}
+
+func (cs *CompassRuntimeAgentSuite) TestApplication() {
+ expectedAppName := "app1"
+ updatedAppName := "app1-updated"
+
+ compassAppName := expectedAppName + random.RandomString(10)
+
+ correctState := false
+ //Create Application in Director
+ applicationID, err := cs.directorClient.RegisterApplication(compassAppName, "Test Application for testing Compass Runtime Agent")
+ cs.Require().NoError(err)
+
+ synchronizedCompassAppName := fmt.Sprintf("mp-%s", compassAppName)
+
+ applicationInterface := cs.applicationsClientSet.ApplicationconnectorV1alpha1().Applications()
+ err = cs.assignApplicationToFormationAndWaitForSync(applicationInterface, synchronizedCompassAppName, applicationID)
+ cs.NoError(err)
+
+ // Compare Application created by Compass Runtime Agent with expected result
+
+ cs.Run("Compass Runtime Agent should create Application", func() {
+ err = cs.appComparator.Compare(cs.T(), expectedAppName, synchronizedCompassAppName)
+ cs.NoError(err)
+
+ correctState = err == nil
+ })
+
+ cs.Run("Update app", func() {
+ if !correctState {
+ cs.T().Skip("App not in correct state")
+ }
+
+ _ = cs.updateAndWait(applicationInterface, synchronizedCompassAppName, applicationID)
+
+ err = cs.appComparator.Compare(cs.T(), updatedAppName, synchronizedCompassAppName)
+ cs.NoError(err)
+
+ correctState = err == nil
+ })
+
+ // Clean up
+ cs.Run("Compass Runtime Agent should remove Application", func() {
+ err = cs.removeApplicationAndWaitForSync(applicationInterface, synchronizedCompassAppName, applicationID)
+ cs.NoError(err)
+ })
+}
+
+func (cs *CompassRuntimeAgentSuite) updateAndWait(appReader ApplicationReader, compassAppName, applicationID string) error {
+ t := cs.T()
+ t.Helper()
+
+ exec := func() error {
+ _, err := cs.directorClient.UpdateApplication(applicationID, updatedDescription)
+ return err
+ }
+
+ verify := func() bool {
+ app, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{})
+ if err != nil {
+ t.Logf("Couldn't get updated: %v", err)
+ }
+
+ return err == nil && app.Spec.Description == updatedDescription
+ }
+
+ return executor.ExecuteAndWaitForCondition{
+ RetryableExecuteFunc: exec,
+ ConditionMetFunc: verify,
+ Tick: checkAppExistsPeriod,
+ Timeout: appUpdateTimeout,
+ }.Do()
+}
+
+func (cs *CompassRuntimeAgentSuite) assignApplicationToFormationAndWaitForSync(appReader ApplicationReader, compassAppName, applicationID string) error {
+ t := cs.T()
+ t.Helper()
+
+ exec := func() error {
+ return cs.directorClient.AssignApplicationToFormation(applicationID, cs.formationName)
+ }
+
+ verify := func() bool {
+ _, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{})
+ if err != nil {
+ t.Logf("Failed to get app: %v", err)
+ }
+
+ return err == nil
+ }
+
+ return executor.ExecuteAndWaitForCondition{
+ RetryableExecuteFunc: exec,
+ ConditionMetFunc: verify,
+ Tick: checkAppExistsPeriod,
+ Timeout: appCreationTimeout,
+ }.Do()
+}
+
+func (cs *CompassRuntimeAgentSuite) removeApplicationAndWaitForSync(appReader ApplicationReader, compassAppName, applicationID string) error {
+ t := cs.T()
+ t.Helper()
+
+ exec := func() error {
+ err := cs.directorClient.UnassignApplication(applicationID, cs.formationName)
+ if err != nil {
+ return err
+ }
+
+ err = cs.directorClient.UnregisterApplication(applicationID)
+ return err
+ }
+
+ verify := func() bool {
+ _, err := appReader.Get(context.Background(), compassAppName, v1.GetOptions{})
+ if errors.IsNotFound(err) {
+ t.Logf("Application was successfully removed by Compass Runtime Agent: %v", err)
+ return true
+ }
+
+ if err != nil {
+ t.Logf("Failed to check whether Application was removed by Compass Runtime Agent: %v", err)
+ }
+
+ return false
+ }
+
+ return executor.ExecuteAndWaitForCondition{
+ RetryableExecuteFunc: exec,
+ ConditionMetFunc: verify,
+ Tick: checkAppExistsPeriod,
+ Timeout: appCreationTimeout,
+ }.Do()
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/comparator.go b/tests/test/compass-runtime-agent/testkit/applications/comparator.go
new file mode 100644
index 00000000..1a9ab03c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/comparator.go
@@ -0,0 +1,125 @@
+package applications
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1"
+ "github.com/stretchr/testify/assert"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+//go:generate mockery --name=ApplicationGetter
+type ApplicationGetter interface {
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error)
+}
+
+func NewComparator(secretComparer Comparator, applicationGetter ApplicationGetter, expectedNamespace, actualNamespace string) (Comparator, error) {
+ return &comparator{
+ secretComparer: secretComparer,
+ applicationGetter: applicationGetter,
+ expectedNamespace: expectedNamespace,
+ actualNamespace: actualNamespace,
+ }, nil
+}
+
+type comparator struct {
+ secretComparer Comparator
+ applicationGetter ApplicationGetter
+ expectedNamespace string
+ actualNamespace string
+}
+
+func (c comparator) Compare(t *testing.T, expected, actual string) error {
+ t.Helper()
+
+ if actual == "" || expected == "" {
+ return errors.New("empty actual or expected application name")
+ }
+
+ actualApp, err := c.applicationGetter.Get(context.Background(), actual, v1.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ expectedApp, err := c.applicationGetter.Get(context.Background(), expected, v1.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ c.compareSpec(t, expectedApp, actualApp)
+ return nil
+}
+
+func (c comparator) compareSpec(t *testing.T, expected, actual *v1alpha1.Application) {
+ t.Helper()
+ a := assert.New(t)
+
+ a.Equal(expected.Spec.Description, actual.Spec.Description, "Description is incorrect")
+ a.Equal(expected.Spec.SkipInstallation, actual.Spec.SkipInstallation, "SkipInstallation is incorrect")
+
+ c.compareServices(t, expected.Spec.Services, actual.Spec.Services)
+
+ a.NotNil(actual.Spec.Labels)
+ a.Equal(actual.Name, actual.Spec.Labels["connected-app"])
+
+ a.Equal(expected.Spec.Tenant, actual.Spec.Tenant, "Tenant is incorrect")
+ a.Equal(expected.Spec.Group, actual.Spec.Group, "Group is incorrect")
+
+ a.Equal(expected.Spec.Tags, actual.Spec.Tags, "Tags is incorrect")
+ a.Equal(expected.Spec.DisplayName, actual.Spec.DisplayName, "DisplayName is incorrect")
+ a.Equal(expected.Spec.ProviderDisplayName, actual.Spec.ProviderDisplayName, "ProviderDisplayName is incorrect")
+ a.Equal(expected.Spec.LongDescription, actual.Spec.LongDescription, "LongDescription is incorrect")
+ a.Equal(expected.Spec.SkipVerify, actual.Spec.SkipVerify, "SkipVerify is incorrect")
+}
+
+func (c comparator) compareServices(t *testing.T, expected, actual []v1alpha1.Service) {
+ t.Helper()
+ a := assert.New(t)
+
+ a.Equal(len(expected), len(actual))
+
+ for i := 0; i < len(actual); i++ {
+ a.Equal(expected[i].Identifier, actual[i].Identifier)
+ a.Equal(expected[i].DisplayName, actual[i].DisplayName)
+ a.Equal(expected[i].Description, actual[i].Description)
+
+ c.compareEntries(t, expected[i].Entries, actual[i].Entries)
+
+ a.Equal(expected[i].AuthCreateParameterSchema, actual[i].AuthCreateParameterSchema)
+ }
+}
+
+func (c comparator) compareEntries(t *testing.T, expected, actual []v1alpha1.Entry) {
+ t.Helper()
+ a := assert.New(t)
+
+ a.Equal(len(expected), len(actual))
+
+ for i := 0; i < len(actual); i++ {
+ a.Equal(expected[i].Type, actual[i].Type)
+ a.Equal(expected[i].TargetUrl, actual[i].TargetUrl)
+ a.Equal(expected[i].SpecificationUrl, actual[i].SpecificationUrl)
+ a.Equal(expected[i].ApiType, actual[i].ApiType)
+
+ c.compareCredentials(t, expected[i].Credentials, actual[i].Credentials)
+
+ a.Equal(expected[i].RequestParametersSecretName, actual[i].RequestParametersSecretName)
+ a.Equal(expected[i].Name, actual[i].Name)
+ }
+}
+
+func (c comparator) compareCredentials(t *testing.T, expected, actual v1alpha1.Credentials) {
+ t.Helper()
+ a := assert.New(t)
+
+ a.Equal(expected.Type, actual.Type)
+
+ err := c.secretComparer.Compare(t, expected.SecretName, actual.SecretName)
+ a.NoError(err)
+
+ a.Equal(expected.AuthenticationUrl, actual.AuthenticationUrl)
+
+ a.Equal(expected.CSRFInfo, actual.CSRFInfo)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go b/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go
new file mode 100644
index 00000000..5583b291
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/comparator_test.go
@@ -0,0 +1,195 @@
+package applications
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1"
+ "github.com/stretchr/testify/mock"
+ "github.com/stretchr/testify/require"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/applications/mocks"
+)
+
+func TestApplicationCrdCompare(t *testing.T) {
+
+ t.Run("should compare applications", func(t *testing.T) {
+ secretComparatorMock := &mocks.Comparator{}
+ applicationGetterMock := &mocks.ApplicationGetter{}
+ actualApp := getTestApp("actual", "actualNamespace", "actualSecret")
+ expectedApp := getTestApp("expected", "expectedNamespace", "expectedSecret")
+
+ secretComparatorMock.On("Compare", mock.Anything, "expectedSecret", "actualSecret").Return(nil)
+ applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(actualApp, nil).Once()
+ applicationGetterMock.On("Get", mock.Anything, "expected", v1.GetOptions{}).Return(expectedApp, nil).Once()
+
+ //when
+ applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expectedNamespace", "actualNamespace")
+ err = applicationComparator.Compare(t, "expected", "actual")
+
+ //then
+ require.NoError(t, err)
+ secretComparatorMock.AssertExpectations(t)
+ applicationGetterMock.AssertExpectations(t)
+ })
+
+ t.Run("should return error when expected or actual application name is empty", func(t *testing.T) {
+ //given
+ secretComparatorMock := &mocks.Comparator{}
+ applicationGetterMock := &mocks.ApplicationGetter{}
+
+ {
+ //when
+ applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual")
+ err = applicationComparator.Compare(t, "expected", "")
+
+ //then
+ require.Error(t, err)
+ }
+
+ {
+ //when
+ applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual")
+ err = applicationComparator.Compare(t, "", "actual")
+
+ //then
+ require.Error(t, err)
+ }
+
+ })
+
+ t.Run("should return error when failed to get actual application", func(t *testing.T) {
+ //given
+ secretComparatorMock := &mocks.Comparator{}
+ applicationGetterMock := &mocks.ApplicationGetter{}
+ actualApp := v1alpha1.Application{}
+
+ applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(&actualApp, errors.New("failed to get actual app")).Once()
+
+ //when
+ applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual")
+ err = applicationComparator.Compare(t, "expected", "actual")
+
+ //then
+ require.Error(t, err)
+ secretComparatorMock.AssertExpectations(t)
+ applicationGetterMock.AssertExpectations(t)
+ })
+
+ t.Run("should return error when failed to get expected application", func(t *testing.T) {
+ //given
+ secretComparatorMock := &mocks.Comparator{}
+ applicationGetterMock := &mocks.ApplicationGetter{}
+ expectedApp := v1alpha1.Application{}
+ actualApp := v1alpha1.Application{}
+
+ applicationGetterMock.On("Get", mock.Anything, "actual", v1.GetOptions{}).Return(&actualApp, nil).Once()
+ applicationGetterMock.On("Get", mock.Anything, "expected", v1.GetOptions{}).Return(&expectedApp, errors.New("failed to get expected app")).Once()
+
+ //when
+ applicationComparator, err := NewComparator(secretComparatorMock, applicationGetterMock, "expected", "actual")
+ err = applicationComparator.Compare(t, "expected", "actual")
+
+ //then
+ require.Error(t, err)
+ secretComparatorMock.AssertExpectations(t)
+ applicationGetterMock.AssertExpectations(t)
+ })
+}
+
+func getTestApp(name, namespace, secretName string) *v1alpha1.Application {
+ //given
+ services := make([]v1alpha1.Service, 0, 0)
+ entries := make([]v1alpha1.Entry, 0, 0)
+
+ credentials := v1alpha1.Credentials{
+ Type: "OAuth",
+ SecretName: secretName,
+ AuthenticationUrl: "authURL",
+ CSRFInfo: &v1alpha1.CSRFInfo{TokenEndpointURL: "csrfTokenURL"},
+ }
+
+ entries = append(entries, v1alpha1.Entry{
+ Type: "api",
+ TargetUrl: "targetURL",
+ SpecificationUrl: "specURL",
+ ApiType: "v1",
+ Credentials: credentials,
+ RequestParametersSecretName: "paramSecret",
+ Name: "test2",
+ ID: "t2",
+ CentralGatewayUrl: "centralURL",
+ AccessLabel: "", //ignore for now
+ GatewayUrl: "",
+ })
+
+ entries = append(entries, v1alpha1.Entry{
+ Type: "api",
+ TargetUrl: "targetURL",
+ SpecificationUrl: "specURL",
+ ApiType: "v1",
+ Credentials: credentials,
+ RequestParametersSecretName: "paramSecret",
+ Name: "test1",
+ ID: "t1",
+ CentralGatewayUrl: "centralURL",
+ AccessLabel: "",
+ GatewayUrl: "",
+ })
+
+ services = append(services, v1alpha1.Service{
+ ID: "serviceTest",
+ Identifier: "st1",
+ Name: "srvTest1",
+ DisplayName: "srvTest1",
+ Description: "srvTest1",
+ Entries: entries,
+ AuthCreateParameterSchema: nil,
+ Labels: nil,
+ LongDescription: "",
+ ProviderDisplayName: "",
+ Tags: nil,
+ })
+
+ services = append(services, v1alpha1.Service{
+ ID: "serviceTest2",
+ Identifier: "st2",
+ Name: "srvTest2",
+ DisplayName: "srvTest2",
+ Description: "srvTest2",
+ Entries: entries,
+ AuthCreateParameterSchema: nil,
+ Labels: nil,
+ LongDescription: "",
+ ProviderDisplayName: "",
+ Tags: nil,
+ })
+
+ return &v1alpha1.Application{
+ TypeMeta: v1.TypeMeta{},
+ ObjectMeta: v1.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ Spec: v1alpha1.ApplicationSpec{
+ Description: "testapp",
+ SkipInstallation: false,
+ Services: services,
+ Labels: map[string]string{"connected-app": name},
+
+ Tenant: "test",
+ Group: "test",
+ CompassMetadata: &v1alpha1.CompassMetadata{
+ ApplicationID: "compassID1",
+ Authentication: v1alpha1.Authentication{ClientIds: []string{"11", "22"}},
+ },
+ Tags: []string{"tag1", "tag2"},
+ DisplayName: "applicationOneDisplay",
+ ProviderDisplayName: "applicationOneDisplay",
+ LongDescription: "applicationOne Test",
+ SkipVerify: true,
+ },
+ }
+
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go b/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go
new file mode 100644
index 00000000..82933f9a
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/mocks/ApplicationGetter.go
@@ -0,0 +1,55 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ context "context"
+
+ mock "github.com/stretchr/testify/mock"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ v1alpha1 "github.com/kyma-project/kyma/components/central-application-gateway/pkg/apis/applicationconnector/v1alpha1"
+)
+
+// ApplicationGetter is an autogenerated mock type for the ApplicationGetter type
+type ApplicationGetter struct {
+ mock.Mock
+}
+
+// Get provides a mock function with given fields: ctx, name, opts
+func (_m *ApplicationGetter) Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.Application, error) {
+ ret := _m.Called(ctx, name, opts)
+
+ var r0 *v1alpha1.Application
+ if rf, ok := ret.Get(0).(func(context.Context, string, v1.GetOptions) *v1alpha1.Application); ok {
+ r0 = rf(ctx, name, opts)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(*v1alpha1.Application)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(context.Context, string, v1.GetOptions) error); ok {
+ r1 = rf(ctx, name, opts)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewApplicationGetter interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewApplicationGetter creates a new instance of ApplicationGetter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewApplicationGetter(t mockConstructorTestingTNewApplicationGetter) *ApplicationGetter {
+ mock := &ApplicationGetter{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go b/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go
new file mode 100644
index 00000000..abccb412
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/mocks/Comparator.go
@@ -0,0 +1,43 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ testing "testing"
+
+ mock "github.com/stretchr/testify/mock"
+)
+
+// Comparator is an autogenerated mock type for the Comparator type
+type Comparator struct {
+ mock.Mock
+}
+
+// Compare provides a mock function with given fields: test, expected, actual
+func (_m *Comparator) Compare(test *testing.T, expected string, actual string) error {
+ ret := _m.Called(test, expected, actual)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(*testing.T, string, string) error); ok {
+ r0 = rf(test, expected, actual)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+type mockConstructorTestingTNewComparator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewComparator creates a new instance of Comparator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewComparator(t mockConstructorTestingTNewComparator) *Comparator {
+ mock := &Comparator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go
new file mode 100644
index 00000000..5573754d
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator.go
@@ -0,0 +1,59 @@
+package applications
+
+import (
+ "context"
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+)
+
+//go:generate mockery --name=Comparator
+type Comparator interface {
+ Compare(test *testing.T, expected, actual string) error
+}
+
+func NewSecretComparator(coreClientSet kubernetes.Interface, expectedNamespace, actualNamespace string) (Comparator, error) {
+ return &secretComparator{
+ coreClientSet: coreClientSet,
+ expectedNamespace: expectedNamespace,
+ actualNamespace: actualNamespace,
+ }, nil
+}
+
+type secretComparator struct {
+ coreClientSet kubernetes.Interface
+ expectedNamespace string
+ actualNamespace string
+}
+
+func (c secretComparator) Compare(t *testing.T, expected, actual string) error {
+ t.Helper()
+
+ if actual == "" && expected == "" {
+ return nil
+ }
+
+ if actual == "" || expected == "" {
+ return errors.New("empty actual or expected secret name")
+ }
+
+ expectedSecretRepo := c.coreClientSet.CoreV1().Secrets(c.expectedNamespace)
+ actualSecretRepo := c.coreClientSet.CoreV1().Secrets(c.actualNamespace)
+
+ expectedSecret, err := expectedSecretRepo.Get(context.Background(), expected, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ actualSecret, err := actualSecretRepo.Get(context.Background(), actual, metav1.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ require.Equal(t, expectedSecret.Data, actualSecret.Data)
+
+ return nil
+}
diff --git a/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go
new file mode 100644
index 00000000..1241a785
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/applications/secretcomparator_test.go
@@ -0,0 +1,117 @@
+package applications
+
+import (
+ "context"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+ v1 "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/fake"
+ core "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+func TestCompare(t *testing.T) {
+
+ t.Run("should return true if secrets are equal", func(t *testing.T) {
+ //given
+ coreV1 := fake.NewSimpleClientset()
+ secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system")
+ require.NoError(t, err)
+ createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("test"), "expected", "test")
+ createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("kyma-system"), "actual", "kyma-system")
+
+ //when
+ err = secretComparator.Compare(t, "expected", "actual")
+
+ // then
+ require.NoError(t, err)
+ })
+
+ t.Run("should return error if failed to read actual secret", func(t *testing.T) {
+ //given
+ coreV1 := fake.NewSimpleClientset()
+ secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system")
+ require.NoError(t, err)
+ createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("test"), "expected", "test")
+
+ //when
+ err = secretComparator.Compare(t, "actual", "expected")
+
+ // then
+ require.Error(t, err)
+ })
+
+ t.Run("should return error if failed to read expected secret", func(t *testing.T) {
+ //given
+ coreV1 := fake.NewSimpleClientset()
+ secretComparator, err := NewSecretComparator(coreV1, "test", "kyma-system")
+ require.NoError(t, err)
+ createFakeCredentialsSecret(t, coreV1.CoreV1().Secrets("kyma-system"), "actual", "kyma-system")
+
+ //when
+ err = secretComparator.Compare(t, "actual", "expected")
+
+ // then
+ require.Error(t, err)
+ })
+
+ t.Run("should return error if expected secret name is empty", func(t *testing.T) {
+ //given
+ secretComparator, err := NewSecretComparator(nil, "test", "kyma-system")
+ require.NoError(t, err)
+
+ //when
+ err = secretComparator.Compare(t, "actual", "")
+
+ // then
+ require.Error(t, err)
+ })
+
+ t.Run("should return error if actual secret name is empty", func(t *testing.T) {
+ //given
+ secretComparator, err := NewSecretComparator(nil, "test", "kyma-system")
+ require.NoError(t, err)
+
+ //when
+ err = secretComparator.Compare(t, "", "expected")
+
+ // then
+ require.Error(t, err)
+ })
+
+ t.Run("should return no error if actual and expected secret name is empty", func(t *testing.T) {
+ //given
+ secretComparator, err := NewSecretComparator(nil, "test", "kyma-system")
+ require.NoError(t, err)
+
+ //when
+ err = secretComparator.Compare(t, "", "")
+
+ // then
+ require.NoError(t, err)
+ })
+}
+
+func createFakeCredentialsSecret(t *testing.T, secrets core.SecretInterface, secretName, namespace string) {
+
+ secret := &v1.Secret{
+ ObjectMeta: meta.ObjectMeta{
+ Name: secretName,
+ Namespace: namespace,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ Data: map[string][]byte{
+ "key1": []byte("val1"),
+ "key2": []byte("val2"),
+ "key3": []byte("val3"),
+ },
+ }
+
+ _, err := secrets.Create(context.Background(), secret, meta.CreateOptions{})
+
+ require.NoError(t, err)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/director/directorclient.go b/tests/test/compass-runtime-agent/testkit/director/directorclient.go
new file mode 100644
index 00000000..6aa33363
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/director/directorclient.go
@@ -0,0 +1,320 @@
+package director
+
+import (
+ "fmt"
+
+ "github.com/kyma-incubator/compass/components/director/pkg/graphql"
+ "github.com/kyma-incubator/compass/components/director/pkg/graphql/graphqlizer"
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+
+ gql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth"
+ gcli "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql"
+)
+
+const (
+ AuthorizationHeader = "Authorization"
+ TenantHeader = "Tenant"
+)
+
+//go:generate mockery --name=Client
+type Client interface {
+ RegisterApplication(appName, displayName string) (string, error)
+ UnregisterApplication(id string) error
+ AssignApplicationToFormation(appId, formationName string) error
+ UnassignApplication(appId, formationName string) error
+ RegisterRuntime(runtimeName string) (string, error)
+ UnregisterRuntime(id string) error
+ RegisterFormation(formationName string) error
+ UnregisterFormation(formationName string) error
+ AssignRuntimeToFormation(runtimeId, formationName string) error
+ GetConnectionToken(runtimeID string) (string, string, error)
+ UpdateApplication(id, newDesc string) (string, error)
+}
+
+type directorClient struct {
+ gqlClient gql.Client
+ queryProvider queryProvider
+ graphqlizer graphqlizer.Graphqlizer
+ token oauth.Token
+ oauthClient oauth.Client
+ tenant string
+}
+
+func NewDirectorClient(gqlClient gql.Client, oauthClient oauth.Client, tenant string) Client {
+
+ return &directorClient{
+ gqlClient: gqlClient,
+ oauthClient: oauthClient,
+ queryProvider: queryProvider{},
+ graphqlizer: graphqlizer.Graphqlizer{},
+ token: oauth.Token{},
+ tenant: tenant,
+ }
+}
+
+func (cc *directorClient) getToken() error {
+ token, err := cc.oauthClient.GetAuthorizationToken()
+ if err != nil {
+ return err
+ }
+
+ if token.EmptyOrExpired() {
+ return errors.New("Obtained empty or expired token")
+ }
+
+ cc.token = token
+ return nil
+}
+
+func (cc *directorClient) RegisterFormation(formationName string) error {
+ log.Infof("Registering Formation")
+
+ queryFunc := func() string { return cc.queryProvider.createFormation(formationName) }
+ execFunc := getExecGraphQLFunc[graphql.Formation](cc)
+ operationDescription := "register Formation"
+ successfulLogMessage := fmt.Sprintf("Successfully registered Formation %s in Director for tenant %s", formationName, cc.tenant)
+
+ return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage)
+}
+
+func (cc *directorClient) UnregisterFormation(formationName string) error {
+ log.Infof("Unregistering Formation")
+ queryFunc := func() string { return cc.queryProvider.deleteFormation(formationName) }
+ execFunc := getExecGraphQLFunc[graphql.Formation](cc)
+ operationDescription := "unregister Formation"
+ successfulLogMessage := fmt.Sprintf("Successfully unregistered Formation %s in Director for tenant %s", formationName, cc.tenant)
+
+ return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage)
+}
+
+func (cc *directorClient) RegisterRuntime(runtimeName string) (string, error) {
+ log.Infof("Registering Runtime")
+ queryFunc := func() string { return cc.queryProvider.registerRuntimeMutation(runtimeName) }
+ execFunc := getExecGraphQLFunc[graphql.Runtime](cc)
+ operationDescription := "register Runtime"
+ successfulLogMessage := fmt.Sprintf("Successfully registered Runtime %s in Director for tenant %s", runtimeName, cc.tenant)
+
+ response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return "", err
+ }
+
+ return response.Result.ID, nil
+}
+
+func (cc *directorClient) UnregisterRuntime(id string) error {
+ log.Infof("Unregistering Runtime")
+
+ queryFunc := func() string { return cc.queryProvider.deleteRuntimeMutation(id) }
+ execFunc := getExecGraphQLFunc[graphql.Runtime](cc)
+ operationDescription := "unregister Runtime"
+ successfulLogMessage := fmt.Sprintf("Successfully unregistered Runtime %s in Director for tenant %s", id, cc.tenant)
+
+ response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return err
+ }
+
+ if response.Result.ID != id {
+ return fmt.Errorf("Failed to unregister runtime %s in Director: received unexpected RuntimeID.", id)
+ }
+
+ return nil
+}
+
+func (cc *directorClient) GetConnectionToken(runtimeId string) (string, string, error) {
+ log.Infof("Requesting one time token for Runtime from Director service")
+
+ queryFunc := func() string { return cc.queryProvider.requestOneTimeTokenMutation(runtimeId) }
+ execFunc := getExecGraphQLFunc[graphql.OneTimeTokenForRuntimeExt](cc)
+ operationDescription := "register application"
+ successfulLogMessage := fmt.Sprintf("Received OneTimeToken for Runtime %s in Director for tenant %s", runtimeId, cc.tenant)
+
+ response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return "", "", err
+ }
+ return response.Result.Token, response.Result.ConnectorURL, nil
+}
+
+func (cc *directorClient) RegisterApplication(appName, displayName string) (string, error) {
+ log.Infof("Registering Application")
+
+ queryFunc := func() string { return cc.queryProvider.registerApplicationFromTemplateMutation(appName, displayName) }
+ execFunc := getExecGraphQLFunc[graphql.Application](cc)
+ operationDescription := "register application"
+ successfulLogMessage := fmt.Sprintf("Successfully registered application %s in Director for tenant %s", appName, cc.tenant)
+
+ result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return "", err
+ }
+
+ id := result.Result.ID
+ _, err = cc.AddBundle(id)
+ return id, err
+}
+
+func (cc *directorClient) AddBundle(appID string) (string, error) {
+ log.Infof("Adding Bundle to Application")
+
+ queryFunc := func() string { return cc.queryProvider.addBundleMutation(appID) }
+ execFunc := getExecGraphQLFunc[graphql.Application](cc)
+ operationDescription := "add bundle"
+ successfulLogMessage := fmt.Sprintf("Successfully added bundle to application with ID %s in Director for tenant %s", appID, cc.tenant)
+
+ result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return "", err
+ }
+ return result.Result.ID, err
+}
+
+func (cc *directorClient) UpdateApplication(id, newDesc string) (string, error) {
+ log.Infof("Updating Application %s", id)
+
+ queryFunc := func() string {
+ return cc.queryProvider.
+ updateApplicationMutation(id, newDesc)
+ }
+ execFunc := getExecGraphQLFunc[graphql.Application](cc)
+ operationDescription := "update application"
+ successfulLogMessage := fmt.Sprintf("Successfully updated application %s in Director for tenant %s", id, cc.tenant)
+
+ result, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return "", err
+ }
+ return result.Result.ID, err
+}
+
+func (cc *directorClient) AssignApplicationToFormation(appId, formationName string) error {
+ log.Infof("Assigning Application to Formation")
+
+ queryFunc := func() string { return cc.queryProvider.assignFormationForAppMutation(appId, formationName) }
+ execFunc := getExecGraphQLFunc[graphql.Formation](cc)
+ operationDescription := "assign Application to Formation"
+ successfulLogMessage := fmt.Sprintf("Successfully assigned application %s to Formation %s in Director for tenant %s", appId, formationName, cc.tenant)
+
+ return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage)
+}
+
+func (cc *directorClient) UnassignApplication(appId, formationName string) error {
+ log.Infof("Unregistering Application from Formation")
+
+ queryFunc := func() string { return cc.queryProvider.unassignFormation(appId, formationName) }
+ execFunc := getExecGraphQLFunc[graphql.Formation](cc)
+ operationDescription := "unregister formation"
+ successfulLogMessage := fmt.Sprintf("Successfully unassigned application %s from Formation %s in Director for tenant %s", appId, formationName, cc.tenant)
+
+ return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage)
+}
+
+func (cc *directorClient) AssignRuntimeToFormation(runtimeId, formationName string) error {
+ log.Infof("Assigning Runtime to Formation")
+
+ queryFunc := func() string { return cc.queryProvider.assignFormationForRuntimeMutation(runtimeId, formationName) }
+ execFunc := getExecGraphQLFunc[graphql.Formation](cc)
+ operationDescription := "assign Runtime to Formation"
+ successfulLogMessage := fmt.Sprintf("Successfully assigned runtime %s to Formation %s in Director for tenant %s", runtimeId, formationName, cc.tenant)
+
+ return executeQuerySkipResponse(queryFunc, execFunc, operationDescription, successfulLogMessage)
+}
+
+func (cc *directorClient) UnregisterApplication(appID string) error {
+ log.Infof("Unregistering Application")
+
+ queryFunc := func() string { return cc.queryProvider.unregisterApplicationMutation(appID) }
+ execFunc := getExecGraphQLFunc[graphql.Application](cc)
+ operationDescription := "Unregistering Application"
+ successfulLogMessage := fmt.Sprintf("Successfully unregister application %s in Director for tenant %s", appID, cc.tenant)
+
+ response, err := executeQuery(queryFunc, execFunc, operationDescription, successfulLogMessage)
+ if err != nil {
+ return err
+ }
+
+ if response.Result.ID != appID {
+ return fmt.Errorf("Failed to unregister Application %s in Director: received unexpected applicationID.", appID)
+ }
+
+ return nil
+}
+
+func (cc *directorClient) executeDirectorGraphQLCall(directorQuery string, tenant string, response interface{}) error {
+ if cc.token.EmptyOrExpired() {
+ log.Infof("Refreshing token to access Director Service")
+ if err := cc.getToken(); err != nil {
+ return err
+ }
+ }
+
+ req := gcli.NewRequest(directorQuery)
+ req.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", cc.token.AccessToken))
+ req.Header.Set(TenantHeader, tenant)
+
+ if err := cc.gqlClient.Do(req, response); err != nil {
+ if egErr, ok := err.(gcli.ExtendedError); ok {
+ return errors.Wrap(egErr, "Failed to execute GraphQL request to Director")
+ }
+ return fmt.Errorf("Failed to execute GraphQL request to Director: %v", err)
+ }
+
+ return nil
+}
+
+type Response[T any] struct {
+ Result *T
+}
+
+func executeQuerySkipResponse[T any](getQueryFunc func() string, executeQueryFunc func(string, *Response[T]) error, operationDescription, successfulLogMessage string) error {
+ _, err := executeQuery(getQueryFunc, executeQueryFunc, operationDescription, successfulLogMessage)
+
+ return err
+}
+
+func executeQuery[T any](getQueryFunc func() string, executeQueryFunc func(string, *Response[T]) error, operationDescription, successfulLogMessage string) (Response[T], error) {
+ query := getQueryFunc()
+
+ var response Response[T]
+ err := executeQueryFunc(query, &response)
+
+ if err != nil {
+ return Response[T]{}, errors.Wrap(err, fmt.Sprintf("Failed to %s in Director. Request failed", operationDescription))
+ }
+
+ // Nil check is necessary due to GraphQL client not checking response code
+ if response.Result == nil {
+ return Response[T]{}, errors.New(fmt.Sprintf("Failed to %s in Director: Received nil response.", operationDescription))
+ }
+
+ log.Infof(successfulLogMessage)
+
+ return response, nil
+}
+
+func getExecGraphQLFunc[T any](cc *directorClient) func(string, *Response[T]) error {
+ return func(query string, result *Response[T]) error {
+ if cc.token.EmptyOrExpired() {
+ log.Infof("Refreshing token to access Director Service")
+ if err := cc.getToken(); err != nil {
+ return err
+ }
+ }
+
+ req := gcli.NewRequest(query)
+ req.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", cc.token.AccessToken))
+ req.Header.Set(TenantHeader, cc.tenant)
+
+ if err := cc.gqlClient.Do(req, result); err != nil {
+ if egErr, ok := err.(gcli.ExtendedError); ok {
+ return errors.Wrap(egErr, "Failed to execute GraphQL request to Director")
+ }
+ return fmt.Errorf("Failed to execute GraphQL request to Director: %v", err)
+ }
+
+ return nil
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go b/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go
new file mode 100644
index 00000000..baa5480c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/director/directorclient_test.go
@@ -0,0 +1,1142 @@
+package director
+
+import (
+ "errors"
+ "fmt"
+ "github.com/kyma-incubator/compass/components/director/pkg/graphql"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+
+ gcli "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql"
+
+ gql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/graphql"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth"
+ oauthmocks "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth/mocks"
+)
+
+const (
+ runtimeTestingID = "test-runtime-ID-12345"
+ runtimeTestingName = "Runtime Test name"
+ testAppName = "Test-application-123"
+ applicationTestingID = "test-application-ID-12345"
+ testAppScenario = "Testing-scenario"
+ testAppDisplayName = "Testing-app-display-name"
+ validTokenValue = "12345"
+ tenantValue = "3e64ebae-38b5-46a0-b1ed-9ccee153a0ae"
+ oneTimeToken = "54321"
+ connectorURL = "https://kyma.cx/connector/graphql"
+
+ expectedRegisterApplicationQuery = `mutation {
+ result: registerApplicationFromTemplate(in: {
+ templateName: "SAP Commerce Cloud"
+ values: [
+ { placeholder: "name", value: "Test-application-123" }
+ { placeholder: "display-name", value: "Testing-app-display-name" }
+ ]
+ }) { id } }`
+
+ expectedAssignAppToFormationQuery = `mutation {
+ result: assignFormation(
+ objectID: "test-application-ID-12345"
+ objectType: APPLICATION
+ formation: { name: "Testing-scenario" }
+ ) { id } }`
+
+ expectedAssignRuntimeToFormationQuery = `mutation {
+ result: assignFormation(
+ objectID: "test-runtime-ID-12345"
+ objectType: RUNTIME
+ formation: { name: "Testing-scenario" }
+ ) { id } }`
+
+ expectedDeleteApplicationQuery = `mutation {
+ result: unregisterApplication(id: "test-application-ID-12345") {
+ id
+ } }`
+
+ expectedRegisterRuntimeQuery = `mutation {
+ result: registerRuntime(in: {
+ name: "Runtime Test name"
+ }) { id } }`
+
+ expectedDeleteRuntimeQuery = `mutation {
+ result: unregisterRuntime(id: "test-runtime-ID-12345") {
+ id
+ }}`
+
+ expectedOneTimeTokenQuery = `mutation {
+ result: requestOneTimeTokenForRuntime(id: "test-runtime-ID-12345") {
+ token connectorURL
+ }}`
+
+ expectedRegisterFormationQuery = `mutation {
+ result: createFormation(formation: {
+ name: "Testing-scenario"
+ }) { id } }`
+
+ expectedDeleteFormationQuery = `mutation {
+ result: deleteFormation(formation: {
+ name: "Testing-scenario"
+ }) { id } }`
+)
+
+var (
+ futureExpirationTime = time.Now().Add(time.Duration(60) * time.Minute).Unix()
+ passedExpirationTime = time.Now().Add(time.Duration(60) * time.Minute * -1).Unix()
+)
+
+func TestDirectorClient_RuntimeRegistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedRegisterRuntimeQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should register runtime and return new runtime ID when the Director access token is valid", func(t *testing.T) {
+ // given
+ responseDescription := "runtime description"
+ expectedResponse := &graphql.Runtime{
+ ID: runtimeTestingID,
+ Name: runtimeTestingName,
+ Description: &responseDescription,
+ }
+
+ expectedID := runtimeTestingID
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.NoError(t, err)
+ assert.Equal(t, expectedID, receivedRuntimeID)
+ })
+
+ t.Run("Should not register runtime and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+
+ t.Run("Should not register runtime and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+
+ t.Run("Should not register Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+
+ t.Run("Should return error when Director fails to register Runtime ", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest)
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterRuntime(runtimeTestingName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+}
+
+func TestDirectorClient_RuntimeUnregistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedDeleteRuntimeQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should unregister runtime of given ID and return no error when the Director access token is valid", func(t *testing.T) {
+ // given
+ responseDescription := "runtime description"
+ expectedResponse := &graphql.Runtime{
+ ID: runtimeTestingID,
+ Name: runtimeTestingName,
+ Description: &responseDescription,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not unregister runtime and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ emptyToken := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister register runtime and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when Director fails to delete Runtime", func(t *testing.T) {
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Runtime](t, expectedRequest)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ // unusual and strange case
+ t.Run("Should return error when Director returns bad ID after Deleting", func(t *testing.T) {
+ // given
+ responseDescription := "runtime description"
+ expectedResponse := &graphql.Runtime{
+ ID: "BadId",
+ Name: runtimeTestingName,
+ Description: &responseDescription,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterRuntime(runtimeTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_FormationRegistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedRegisterFormationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should register Formation and return no error when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Formation{
+ Name: testAppScenario,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not register Formation and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not register Formation and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not register Formation and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) {
+ cfg, ok := r.(*Response[graphql.Formation])
+ require.True(t, ok)
+ assert.Empty(t, cfg.Result)
+ cfg.Result = nil
+ })
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when Director fails to register Formation ", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest)
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.RegisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_FormationUnregistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedDeleteFormationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should unregister Formation of given name and return no error when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Formation{
+ Name: testAppScenario,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not unregister Formation and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ emptyToken := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister register Formation and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister Formation and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest)
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when Director fails to delete Formation", func(t *testing.T) {
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Formation](t, expectedRequest)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterFormation(testAppScenario)
+
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_ApplicationRegistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedRegisterApplicationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should register application and return new application ID when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Application{
+ Name: testAppName,
+ BaseEntity: &graphql.BaseEntity{
+ ID: applicationTestingID,
+ },
+ }
+ expectedID := applicationTestingID
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.NoError(t, err)
+ assert.Equal(t, expectedID, receivedApplicationID)
+ })
+
+ t.Run("Should not register application and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedApplicationID)
+ })
+
+ t.Run("Should not register Application and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedApplicationID)
+ })
+
+ t.Run("Should not register Application and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedApplicationID)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest)
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedApplicationID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedApplicationID)
+ })
+
+ t.Run("Should return error when Director fails to register Runtime ", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest)
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedRuntimeID, err := configClient.RegisterApplication(testAppName, testAppDisplayName)
+
+ // then
+ assert.Error(t, err)
+ assert.Empty(t, receivedRuntimeID)
+ })
+}
+
+func TestDirectorClient_ApplicationAssignToFormation(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedAssignAppToFormationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should assign application to formation and return new application ID when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Formation{
+ Name: testAppScenario,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.AssignApplicationToFormation(applicationTestingID, testAppScenario)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not assign application to formation and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.AssignApplicationToFormation(applicationTestingID, testAppScenario)
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_RuntimeAssignToFormation(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedAssignRuntimeToFormationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should assign application to formation and return new application ID when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Formation{
+ Name: testAppScenario,
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.AssignRuntimeToFormation(runtimeTestingID, testAppScenario)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not assign application to formation and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.AssignRuntimeToFormation(runtimeTestingID, testAppScenario)
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_ApplicationUnregistering(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedDeleteApplicationQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should unregister runtime of given ID and return no error when the Director access token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Application{
+ Name: testAppName,
+ BaseEntity: &graphql.BaseEntity{
+ ID: applicationTestingID,
+ },
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.NoError(t, err)
+ })
+
+ t.Run("Should not unregister runtime and return an error when the Director access token is empty", func(t *testing.T) {
+ // given
+ emptyToken := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(emptyToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister register runtime and return an error when the Director access token is expired", func(t *testing.T) {
+ // given
+ expiredToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(expiredToken, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should not unregister Runtime and return error when the client fails to get an access token for Director", func(t *testing.T) {
+ // given
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(oauth.Token{}, errors.New("Failed token error"))
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when the result of the call to Director service is nil", func(t *testing.T) {
+ // given
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ t.Run("Should return error when Director fails to delete Runtime", func(t *testing.T) {
+ // given
+ gqlClient := newFailingQueryAssertClient[graphql.Application](t, expectedRequest)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+
+ // unusual and strange case
+ t.Run("Should return error when Director returns bad ID after Deleting", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.Application{
+ Name: testAppName,
+ BaseEntity: &graphql.BaseEntity{
+ ID: "badID",
+ },
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+
+ validToken := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(validToken, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ err := configClient.UnregisterApplication(applicationTestingID)
+
+ // then
+ assert.Error(t, err)
+ })
+}
+
+func TestDirectorClient_GetConnectionToken(t *testing.T) {
+ expectedRequest := gcli.NewRequest(expectedOneTimeTokenQuery)
+ expectedRequest.Header.Set(AuthorizationHeader, fmt.Sprintf("Bearer %s", validTokenValue))
+ expectedRequest.Header.Set(TenantHeader, tenantValue)
+
+ t.Run("Should return OneTimeToken when Oauth Token is valid", func(t *testing.T) {
+ // given
+ expectedResponse := &graphql.OneTimeTokenForRuntimeExt{
+ OneTimeTokenForRuntime: graphql.OneTimeTokenForRuntime{
+ TokenWithURL: graphql.TokenWithURL{
+ Token: oneTimeToken,
+ ConnectorURL: connectorURL,
+ },
+ },
+ }
+
+ gqlClient := newQueryAssertClient(t, expectedRequest, expectedResponse)
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID)
+
+ // then
+ require.NoError(t, err)
+ require.NotEmpty(t, receivedOneTimeToken)
+ assert.Equal(t, oneTimeToken, receivedOneTimeToken)
+ assert.Equal(t, connectorURL, receivedConnectorURL)
+ })
+
+ t.Run("Should return error when Oauth Token is empty", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: "",
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID)
+
+ // then
+ require.Error(t, err)
+ require.Empty(t, receivedConnectorURL)
+ require.Empty(t, receivedOneTimeToken)
+ })
+
+ t.Run("Should return error when Oauth Token is expired", func(t *testing.T) {
+ // given
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: passedExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(nil, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID)
+
+ // then
+ require.Error(t, err)
+ require.Empty(t, receivedConnectorURL)
+ require.Empty(t, receivedOneTimeToken)
+ })
+
+ t.Run("Should return error when Director call returns nil response", func(t *testing.T) {
+ // given
+ gqlClient := newQueryAssertClient[graphql.OneTimeTokenForRuntimeExt](t, expectedRequest, nil)
+
+ token := oauth.Token{
+ AccessToken: validTokenValue,
+ Expiration: futureExpirationTime,
+ }
+
+ mockedOAuthClient := &oauthmocks.Client{}
+ mockedOAuthClient.On("GetAuthorizationToken").Return(token, nil)
+
+ configClient := NewDirectorClient(gqlClient, mockedOAuthClient, tenantValue)
+
+ // when
+ receivedOneTimeToken, receivedConnectorURL, err := configClient.GetConnectionToken(runtimeTestingID)
+
+ // then
+ require.Error(t, err)
+ require.Empty(t, receivedConnectorURL)
+ require.Empty(t, receivedOneTimeToken)
+ })
+}
+
+func newQueryAssertClient[T any](t *testing.T, expectedRequest *gcli.Request, expectedResponse *T) gql.Client {
+ return gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) {
+ cfg, ok := r.(*Response[T])
+ require.True(t, ok)
+ assert.Empty(t, cfg.Result)
+
+ if expectedResponse != nil {
+ cfg.Result = expectedResponse
+ }
+ })
+}
+
+func newFailingQueryAssertClient[T any](t *testing.T, expectedRequest *gcli.Request) gql.Client {
+ return gql.NewQueryAssertClient(t, nil, []*gcli.Request{expectedRequest}, func(t *testing.T, r interface{}) {
+ cfg, ok := r.(*Response[T])
+ require.True(t, ok)
+ assert.Empty(t, cfg.Result)
+ cfg.Result = nil
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go
new file mode 100644
index 00000000..ccfa1a77
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/director/mocks/Client.go
@@ -0,0 +1,193 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+ mock.Mock
+}
+
+// AssignApplicationToFormation provides a mock function with given fields: appId, formationName
+func (_m *Client) AssignApplicationToFormation(appId string, formationName string) error {
+ ret := _m.Called(appId, formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(appId, formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// AssignRuntimeToFormation provides a mock function with given fields: runtimeId, formationName
+func (_m *Client) AssignRuntimeToFormation(runtimeId string, formationName string) error {
+ ret := _m.Called(runtimeId, formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(runtimeId, formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// GetConnectionToken provides a mock function with given fields: runtimeID
+func (_m *Client) GetConnectionToken(runtimeID string) (string, string, error) {
+ ret := _m.Called(runtimeID)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string) string); ok {
+ r0 = rf(runtimeID)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 string
+ if rf, ok := ret.Get(1).(func(string) string); ok {
+ r1 = rf(runtimeID)
+ } else {
+ r1 = ret.Get(1).(string)
+ }
+
+ var r2 error
+ if rf, ok := ret.Get(2).(func(string) error); ok {
+ r2 = rf(runtimeID)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// RegisterApplication provides a mock function with given fields: appName, displayName
+func (_m *Client) RegisterApplication(appName string, displayName string) (string, error) {
+ ret := _m.Called(appName, displayName)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string, string) string); ok {
+ r0 = rf(appName, displayName)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string, string) error); ok {
+ r1 = rf(appName, displayName)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// RegisterFormation provides a mock function with given fields: formationName
+func (_m *Client) RegisterFormation(formationName string) error {
+ ret := _m.Called(formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// RegisterRuntime provides a mock function with given fields: runtimeName
+func (_m *Client) RegisterRuntime(runtimeName string) (string, error) {
+ ret := _m.Called(runtimeName)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string) string); ok {
+ r0 = rf(runtimeName)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(runtimeName)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// UnassignApplication provides a mock function with given fields: appId, formationName
+func (_m *Client) UnassignApplication(appId string, formationName string) error {
+ ret := _m.Called(appId, formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(appId, formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// UnregisterApplication provides a mock function with given fields: id
+func (_m *Client) UnregisterApplication(id string) error {
+ ret := _m.Called(id)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(id)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// UnregisterFormation provides a mock function with given fields: formationName
+func (_m *Client) UnregisterFormation(formationName string) error {
+ ret := _m.Called(formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// UnregisterRuntime provides a mock function with given fields: id
+func (_m *Client) UnregisterRuntime(id string) error {
+ ret := _m.Called(id)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(id)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+type mockConstructorTestingTNewClient interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewClient(t mockConstructorTestingTNewClient) *Client {
+ mock := &Client{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go b/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go
new file mode 100644
index 00000000..ee496d09
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/director/mocks/DirectorClient.go
@@ -0,0 +1,60 @@
+// Code generated by mockery v2.14.0. DO NOT EDIT.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+
+// DirectorClient is an autogenerated mock type for the DirectorClient type
+type DirectorClient struct {
+ mock.Mock
+}
+
+// RegisterApplication provides a mock function with given fields: appName, scenario, tenant
+func (_m *DirectorClient) RegisterApplication(appName string, scenario string, tenant string) (string, error) {
+ ret := _m.Called(appName, scenario, tenant)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string, string, string) string); ok {
+ r0 = rf(appName, scenario, tenant)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string, string, string) error); ok {
+ r1 = rf(appName, scenario, tenant)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// UnregisterApplication provides a mock function with given fields: id, tenant
+func (_m *DirectorClient) UnregisterApplication(id string, tenant string) error {
+ ret := _m.Called(id, tenant)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(id, tenant)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+type mockConstructorTestingTNewDirectorClient interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewDirectorClient creates a new instance of DirectorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewDirectorClient(t mockConstructorTestingTNewDirectorClient) *DirectorClient {
+ mock := &DirectorClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/director/queryprovider.go b/tests/test/compass-runtime-agent/testkit/director/queryprovider.go
new file mode 100644
index 00000000..c7cec324
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/director/queryprovider.go
@@ -0,0 +1,127 @@
+package director
+
+import "fmt"
+
+type queryProvider struct{}
+
+func (qp queryProvider) createFormation(formationName string) string {
+ return fmt.Sprintf(`mutation {
+ result: createFormation(formation: {
+ name: "%s"
+ }) { id } }`, formationName)
+}
+
+func (qp queryProvider) deleteFormation(formationName string) string {
+ return fmt.Sprintf(`mutation {
+ result: deleteFormation(formation: {
+ name: "%s"
+ }) { id } }`, formationName)
+}
+
+func (qp queryProvider) registerApplicationFromTemplateMutation(appName, displayName string) string {
+ return fmt.Sprintf(`mutation {
+ result: registerApplicationFromTemplate(in: {
+ templateName: "SAP Commerce Cloud"
+ values: [
+ { placeholder: "name", value: "%s" }
+ { placeholder: "display-name", value: "%s" }
+ ]
+ }) { id } }`, appName, displayName)
+}
+
+func (qp queryProvider) addBundleMutation(appID string) string {
+ return fmt.Sprintf(`mutation {
+ result: addBundle(
+ applicationID: "%s"
+ in: {
+ name: "bndl-app-1"
+ description: "Foo bar"
+ apiDefinitions: [
+ {
+ name: "comments-v1"
+ description: "api for adding comments"
+ targetURL: "http://mywordpress.com/comments"
+ group: "comments"
+ spec: {
+ data: "{\"openapi\":\"3.0.2\"}"
+ type: OPEN_API
+ format: YAML
+ }
+ version: {
+ value: "1.0.0"
+ deprecated: true
+ deprecatedSince: "v5"
+ forRemoval: false
+ }
+ }
+ ]
+ }
+ ) { id } }`, appID)
+}
+
+func (qp queryProvider) updateApplicationMutation(id, description string) string {
+ return fmt.Sprintf(`mutation {
+ result: updateApplication(
+ id: "%s"
+ in: {description: "%s"
+ }) { id } }`,
+ id, description)
+}
+
+func (qp queryProvider) assignFormationForAppMutation(applicationId, formationName string) string {
+ return fmt.Sprintf(`mutation {
+ result: assignFormation(
+ objectID: "%s"
+ objectType: APPLICATION
+ formation: { name: "%s" }
+ ) { id } }`, applicationId, formationName)
+}
+
+func (qp queryProvider) unassignFormation(applicationId, formationName string) string {
+ return fmt.Sprintf(`mutation {
+ result: unassignFormation(
+ objectID: "%s"
+ objectType: APPLICATION
+ formation: { name: "%s" }
+ ) {
+ name
+ }
+}`, applicationId, formationName)
+}
+
+func (qp queryProvider) assignFormationForRuntimeMutation(runtimeId, formationName string) string {
+ return fmt.Sprintf(`mutation {
+ result: assignFormation(
+ objectID: "%s"
+ objectType: RUNTIME
+ formation: { name: "%s" }
+ ) { id } }`, runtimeId, formationName)
+}
+
+func (qp queryProvider) unregisterApplicationMutation(applicationID string) string {
+ return fmt.Sprintf(`mutation {
+ result: unregisterApplication(id: "%s") {
+ id
+ } }`, applicationID)
+}
+
+func (qp queryProvider) deleteRuntimeMutation(runtimeID string) string {
+ return fmt.Sprintf(`mutation {
+ result: unregisterRuntime(id: "%s") {
+ id
+ }}`, runtimeID)
+}
+
+func (qp queryProvider) registerRuntimeMutation(runtimeName string) string {
+ return fmt.Sprintf(`mutation {
+ result: registerRuntime(in: {
+ name: "%s"
+ }) { id } }`, runtimeName)
+}
+
+func (qp queryProvider) requestOneTimeTokenMutation(runtimeID string) string {
+ return fmt.Sprintf(`mutation {
+ result: requestOneTimeTokenForRuntime(id: "%s") {
+ token connectorURL
+ }}`, runtimeID)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/executor/toolkit.go b/tests/test/compass-runtime-agent/testkit/executor/toolkit.go
new file mode 100644
index 00000000..f132c9b5
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/executor/toolkit.go
@@ -0,0 +1,53 @@
+package executor
+
+import (
+ "context"
+ "github.com/avast/retry-go"
+ "github.com/pkg/errors"
+ "time"
+)
+
+type RetryableExecuteFunc func() error
+type ConditionMet func() bool
+
+type ExecuteAndWaitForCondition struct {
+ RetryableExecuteFunc RetryableExecuteFunc
+ ConditionMetFunc ConditionMet
+ Tick time.Duration
+ Timeout time.Duration
+}
+
+func (e ExecuteAndWaitForCondition) Do() error {
+
+ err := retry.Do(func() error {
+ return e.RetryableExecuteFunc()
+ })
+
+ if err != nil {
+ return err
+ }
+ ctx, cancel := context.WithTimeout(context.Background(), e.Timeout)
+ defer cancel()
+
+ ticker := time.NewTicker(e.Tick)
+
+ for {
+ select {
+ case <-ticker.C:
+ {
+ res := e.ConditionMetFunc()
+
+ if res {
+ ticker.Stop()
+ return nil
+ }
+
+ }
+ case <-ctx.Done():
+ {
+ ticker.Stop()
+ return errors.New("Condition not met")
+ }
+ }
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go b/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go
new file mode 100644
index 00000000..8a634276
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/executor/toolkit_test.go
@@ -0,0 +1,101 @@
+package executor
+
+import (
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/require"
+ "testing"
+ "time"
+)
+
+func TestToolkit(t *testing.T) {
+ t.Run("Should return no error when verify function returns true", func(t *testing.T) {
+ // given
+ executeAndWait := ExecuteAndWaitForCondition{
+ RetryableExecuteFunc: func() error {
+ return nil
+ },
+ ConditionMetFunc: func() bool {
+ return true
+ },
+ Tick: 10 * time.Second,
+ Timeout: 1 * time.Minute,
+ }
+
+ // when
+ err := executeAndWait.Do()
+
+ //then
+ require.NoError(t, err)
+ })
+
+ t.Run("Retry when exec function fails", func(t *testing.T) {
+ // given
+ counter := 1
+
+ executeAndWait := ExecuteAndWaitForCondition{
+
+ RetryableExecuteFunc: func() error {
+ if counter < 3 {
+ counter++
+ return errors.New("failed")
+ }
+
+ return nil
+ },
+ ConditionMetFunc: func() bool {
+ return true
+ },
+ Tick: 10 * time.Second,
+ Timeout: 1 * time.Minute,
+ }
+
+ // when
+ err := executeAndWait.Do()
+
+ //then
+ require.NoError(t, err)
+ require.Greater(t, counter, 2)
+ })
+
+ t.Run("Return error when exec function constantly fails", func(t *testing.T) {
+ // given
+ executeAndWait := ExecuteAndWaitForCondition{
+
+ RetryableExecuteFunc: func() error {
+ return errors.New("call failed")
+ },
+ ConditionMetFunc: func() bool {
+ return true
+ },
+ Tick: 10 * time.Second,
+ Timeout: 1 * time.Minute,
+ }
+
+ // when
+ err := executeAndWait.Do()
+
+ //then
+ require.Error(t, err)
+ })
+
+ t.Run("Return error when verify function constantly returns false", func(t *testing.T) {
+ // given
+ executeAndWait := ExecuteAndWaitForCondition{
+
+ RetryableExecuteFunc: func() error {
+ return nil
+ },
+ ConditionMetFunc: func() bool {
+ return false
+ },
+ Tick: 10 * time.Second,
+ Timeout: 1 * time.Minute,
+ }
+
+ // when
+ err := executeAndWait.Do()
+
+ //then
+ require.Error(t, err)
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/graphql/client.go b/tests/test/compass-runtime-agent/testkit/graphql/client.go
new file mode 100644
index 00000000..888094ab
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/graphql/client.go
@@ -0,0 +1,76 @@
+package graphql
+
+import (
+ "context"
+ "crypto/tls"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql"
+ "net/http"
+ "time"
+
+ "github.com/sirupsen/logrus"
+)
+
+const (
+ timeout = 30 * time.Second
+)
+
+type ClientConstructor func(certificate *tls.Certificate, graphqlEndpoint string, enableLogging bool, insecureConfigFetch bool) (Client, error)
+
+//go:generate mockery --name=Client
+type Client interface {
+ Do(req *graphql.Request, res interface{}) error
+}
+
+type client struct {
+ gqlClient *graphql.Client
+ logs []string
+ logging bool
+}
+
+func NewGraphQLClient(graphqlEndpoint string, enableLogging bool, insecureSkipVerify bool) Client {
+ httpClient := &http.Client{
+ Transport: &http.Transport{
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: insecureSkipVerify},
+ },
+ }
+
+ gqlClient := graphql.NewClient(graphqlEndpoint, graphql.WithHTTPClient(httpClient))
+
+ client := &client{
+ gqlClient: gqlClient,
+ logging: enableLogging,
+ logs: []string{},
+ }
+
+ client.gqlClient.Log = client.addLog
+
+ return client
+}
+
+func (c *client) Do(req *graphql.Request, res interface{}) error {
+ ctx, cancel := context.WithTimeout(context.Background(), timeout)
+ defer cancel()
+
+ c.clearLogs()
+ err := c.gqlClient.Run(ctx, req, res)
+ if err != nil {
+ for _, l := range c.logs {
+ if l != "" {
+ logrus.Info(l)
+ }
+ }
+ }
+ return err
+}
+
+func (c *client) addLog(log string) {
+ if !c.logging {
+ return
+ }
+
+ c.logs = append(c.logs, log)
+}
+
+func (c *client) clearLogs() {
+ c.logs = []string{}
+}
diff --git a/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go b/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go
new file mode 100644
index 00000000..1bfe2a77
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/graphql/gql_client_testkit.go
@@ -0,0 +1,47 @@
+package graphql
+
+import (
+ "errors"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+type QueryAssertClient struct {
+ t *testing.T
+ expectedRequests []*graphql.Request
+ err error
+ modifyResponseFunc ModifyResponseFunc
+}
+
+type ModifyResponseFunc []func(t *testing.T, r interface{})
+
+func (c *QueryAssertClient) Do(req *graphql.Request, res interface{}) error {
+ if len(c.expectedRequests) == 0 {
+ return errors.New("no more requests were expected")
+ }
+
+ assert.Equal(c.t, c.expectedRequests[0], req)
+ if len(c.expectedRequests) > 1 {
+ c.expectedRequests = c.expectedRequests[1:]
+ }
+
+ if len(c.modifyResponseFunc) > 0 {
+ c.modifyResponseFunc[0](c.t, res)
+ if len(c.modifyResponseFunc) > 1 {
+ c.modifyResponseFunc = c.modifyResponseFunc[1:]
+ }
+ }
+
+ return c.err
+}
+
+func NewQueryAssertClient(t *testing.T, err error, expectedReq []*graphql.Request, modifyResponseFunc ...func(t *testing.T, r interface{})) Client {
+ return &QueryAssertClient{
+ t: t,
+ expectedRequests: expectedReq,
+ err: err,
+ modifyResponseFunc: modifyResponseFunc,
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go
new file mode 100644
index 00000000..e6a42c6c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/graphql/mocks/Client.go
@@ -0,0 +1,42 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ graphql "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/third_party/machinebox/graphql"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields: req, res
+func (_m *Client) Do(req *graphql.Request, res interface{}) error {
+ ret := _m.Called(req, res)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(*graphql.Request, interface{}) error); ok {
+ r0 = rf(req, res)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+type mockConstructorTestingTNewClient interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewClient(t mockConstructorTestingTNewClient) *Client {
+ mock := &Client{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go b/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go
new file mode 100644
index 00000000..530e11af
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/certificatesecrets_test.go
@@ -0,0 +1,78 @@
+package init
+
+import (
+ "context"
+ "github.com/stretchr/testify/require"
+ v1 "k8s.io/api/core/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/fake"
+ "testing"
+)
+
+func TestCertificateSecrets(t *testing.T) {
+ t.Run("should return rollback function that will remove secrets", func(t *testing.T) {
+ // given
+ fakeKubernetesInterface := fake.NewSimpleClientset()
+
+ // when
+ configurator := NewCertificateSecretConfigurator(fakeKubernetesInterface)
+ rollbackFunc, err := configurator.Do("newCaSecret", "newClientSetSecret")
+
+ // then
+ require.NoError(t, err)
+
+ // given
+ caCertSecret := createSecret("newCaSecret", IstioSystemNamespace)
+ clientCertSecret := createSecret("newClientSetSecret", CompassSystemNamespace)
+
+ _, err = fakeKubernetesInterface.CoreV1().Secrets(IstioSystemNamespace).Create(context.TODO(), caCertSecret, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ _, err = fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.TODO(), clientCertSecret, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ // when
+ err = rollbackFunc()
+ require.NoError(t, err)
+
+ // then
+ _, err = fakeKubernetesInterface.CoreV1().Secrets("test").Get(context.TODO(), "newCaSecret", meta.GetOptions{})
+ require.Error(t, err)
+ require.True(t, k8serrors.IsNotFound(err))
+
+ _, err = fakeKubernetesInterface.CoreV1().Secrets("test").Get(context.TODO(), "newClientSetSecret", meta.GetOptions{})
+ require.Error(t, err)
+ require.True(t, k8serrors.IsNotFound(err))
+ })
+
+ t.Run("should not return error when rollback function tries to delete non-existent secrets", func(t *testing.T) {
+ // given
+ fakeKubernetesInterface := fake.NewSimpleClientset()
+
+ // when
+ configurator := NewCertificateSecretConfigurator(fakeKubernetesInterface)
+ rollbackFunc, err := configurator.Do("newCaSecret", "newClientSetSecret")
+
+ // then
+ require.NoError(t, err)
+
+ // when
+ err = rollbackFunc()
+ require.NoError(t, err)
+ })
+ // TODO: consider a case when rollback function fails
+}
+
+func createSecret(name, namespace string) *v1.Secret {
+ return &v1.Secret{
+ ObjectMeta: meta.ObjectMeta{
+ Name: name,
+ Namespace: namespace,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go b/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go
new file mode 100644
index 00000000..3141ca9c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/certificatessecrets.go
@@ -0,0 +1,41 @@
+package init
+
+import (
+ "github.com/hashicorp/go-multierror"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "k8s.io/client-go/kubernetes"
+)
+
+type certificatesSecretsConfigurator struct {
+ kubernetesInterface kubernetes.Interface
+}
+
+func NewCertificateSecretConfigurator(kubernetesInterface kubernetes.Interface) certificatesSecretsConfigurator {
+ return certificatesSecretsConfigurator{
+ kubernetesInterface: kubernetesInterface,
+ }
+}
+
+func (csc certificatesSecretsConfigurator) Do(newCASecretName, newClusterCertSecretName string) (types.RollbackFunc, error) {
+ // Original secrets created by Compass Runtime Agent are left intact so that they can be restored after the test.
+ // As part of the test preparation new secret names are passed to the Compass Runtime Agent Deployment. Rollback function needs to delete those.
+ return csc.getRollbackFunction(newCASecretName, newClusterCertSecretName), nil
+}
+
+func (csc certificatesSecretsConfigurator) getRollbackFunction(caSecretName, clusterCertSecretName string) types.RollbackFunc {
+ return func() error {
+ var result *multierror.Error
+
+ err := deleteSecretWithRetry(csc.kubernetesInterface, caSecretName, IstioSystemNamespace)
+ if err != nil {
+ multierror.Append(result, err)
+ }
+
+ err = deleteSecretWithRetry(csc.kubernetesInterface, clusterCertSecretName, CompassSystemNamespace)
+ if err != nil {
+ multierror.Append(result, err)
+ }
+
+ return result.ErrorOrNil()
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/compass.go b/tests/test/compass-runtime-agent/testkit/init/compass.go
new file mode 100644
index 00000000..454b85bc
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/compass.go
@@ -0,0 +1,53 @@
+package init
+
+import (
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+)
+
+type compassconfigurator struct {
+ directorClient types.DirectorClient
+ tenant string
+}
+
+func NewCompassConfigurator(directorClient types.DirectorClient, tenant string) compassconfigurator {
+ return compassconfigurator{
+ directorClient: directorClient,
+ tenant: tenant,
+ }
+}
+
+func (cc compassconfigurator) Do(runtimeName, formationName string) (types.CompassRuntimeAgentConfig, types.RollbackFunc, error) {
+ runtimeID, err := cc.directorClient.RegisterRuntime(runtimeName)
+ if err != nil {
+ return types.CompassRuntimeAgentConfig{}, nil, err
+ }
+
+ unregisterRuntimeRollbackFunc := func() error { return cc.directorClient.UnregisterRuntime(runtimeID) }
+
+ err = cc.directorClient.RegisterFormation(formationName)
+ if err != nil {
+ return types.CompassRuntimeAgentConfig{}, unregisterRuntimeRollbackFunc, err
+ }
+
+ unregisterFormationRollbackFunc := func() error { return cc.directorClient.UnregisterFormation(formationName) }
+ rollBackFunc := newRollbackFunc(unregisterRuntimeRollbackFunc, unregisterFormationRollbackFunc)
+
+ err = cc.directorClient.AssignRuntimeToFormation(runtimeID, formationName)
+ if err != nil {
+ return types.CompassRuntimeAgentConfig{}, rollBackFunc, err
+ }
+
+ token, compassConnectorUrl, err := cc.directorClient.GetConnectionToken(runtimeID)
+ if err != nil {
+ return types.CompassRuntimeAgentConfig{}, rollBackFunc, err
+ }
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: compassConnectorUrl,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: cc.tenant,
+ }
+
+ return config, rollBackFunc, nil
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/compass_test.go b/tests/test/compass-runtime-agent/testkit/init/compass_test.go
new file mode 100644
index 00000000..d47ca08d
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/compass_test.go
@@ -0,0 +1,159 @@
+package init
+
+import (
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types/mocks"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestCompassConfigurator(t *testing.T) {
+ runtimeName := "runtime"
+ runtimeID := "runtimeID"
+ formationName := "formation"
+ connectionToken := "token"
+ connectorURL := "connector.com"
+ tenant := "tenant"
+
+ t.Run("should register Runtime, Formation and get connection token", func(t *testing.T) {
+ // given
+ directorClientMock := &mocks.DirectorClient{}
+ directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil)
+ directorClientMock.On("RegisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil)
+ directorClientMock.On("UnregisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("GetConnectionToken", runtimeID).Return(connectionToken, connectorURL, nil)
+ directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(nil)
+
+ // when
+ compassConfigurator := NewCompassConfigurator(directorClientMock, tenant)
+ require.NotNil(t, compassConfigurator)
+
+ compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName)
+
+ // then
+ require.NotNil(t, rollbackFunc)
+ require.NoError(t, err)
+ require.Equal(t, runtimeID, compassRuntimeAgentConfig.RuntimeID)
+ require.Equal(t, tenant, compassRuntimeAgentConfig.Tenant)
+ require.Equal(t, connectionToken, compassRuntimeAgentConfig.Token)
+ require.Equal(t, connectorURL, compassRuntimeAgentConfig.ConnectorUrl)
+
+ // when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ directorClientMock.AssertExpectations(t)
+ })
+
+ t.Run("should fail when failed to register Runtime", func(t *testing.T) {
+ // given
+ directorClientMock := &mocks.DirectorClient{}
+ directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, errors.New("some error"))
+
+ // when
+ compassConfigurator := NewCompassConfigurator(directorClientMock, tenant)
+ require.NotNil(t, compassConfigurator)
+
+ compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName)
+
+ // then
+ require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig)
+ require.Nil(t, rollbackFunc)
+ require.Error(t, err)
+ directorClientMock.AssertExpectations(t)
+ })
+
+ t.Run("should fail when failed to register Formation", func(t *testing.T) {
+ // given
+ directorClientMock := &mocks.DirectorClient{}
+ directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil)
+ directorClientMock.On("RegisterFormation", formationName).Return(errors.New("some error"))
+ directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil)
+
+ // when
+ compassConfigurator := NewCompassConfigurator(directorClientMock, tenant)
+ require.NotNil(t, compassConfigurator)
+
+ compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName)
+
+ // then
+ require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig)
+ require.NotNil(t, rollbackFunc)
+ require.Error(t, err)
+
+ // when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ directorClientMock.AssertExpectations(t)
+ })
+
+ t.Run("should fail when failed to assign Runtime to Formation", func(t *testing.T) {
+ // given
+ directorClientMock := &mocks.DirectorClient{}
+ directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil)
+ directorClientMock.On("RegisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil)
+ directorClientMock.On("UnregisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(errors.New("some error"))
+
+ // when
+ compassConfigurator := NewCompassConfigurator(directorClientMock, tenant)
+ require.NotNil(t, compassConfigurator)
+
+ compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName)
+
+ // then
+ require.NotNil(t, compassConfigurator)
+ require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig)
+ require.NotNil(t, rollbackFunc)
+ require.Error(t, err)
+
+ // when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ directorClientMock.AssertExpectations(t)
+ })
+
+ t.Run("should fail when failed to get connection token", func(t *testing.T) {
+ // given
+ directorClientMock := &mocks.DirectorClient{}
+ directorClientMock.On("RegisterRuntime", runtimeName).Return(runtimeID, nil)
+ directorClientMock.On("RegisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("UnregisterRuntime", runtimeID).Return(nil)
+ directorClientMock.On("UnregisterFormation", formationName).Return(nil)
+
+ directorClientMock.On("AssignRuntimeToFormation", runtimeID, formationName).Return(nil)
+ directorClientMock.On("GetConnectionToken", runtimeID).Return("", "", errors.New("some error"))
+
+ // when
+ compassConfigurator := NewCompassConfigurator(directorClientMock, tenant)
+ require.NotNil(t, compassConfigurator)
+
+ compassRuntimeAgentConfig, rollbackFunc, err := compassConfigurator.Do(runtimeName, formationName)
+
+ // then
+ require.NotNil(t, compassConfigurator)
+ require.Equal(t, types.CompassRuntimeAgentConfig{}, compassRuntimeAgentConfig)
+ require.NotNil(t, rollbackFunc)
+ require.Error(t, err)
+
+ // when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ directorClientMock.AssertExpectations(t)
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/compassconnection.go b/tests/test/compass-runtime-agent/testkit/init/compassconnection.go
new file mode 100644
index 00000000..a5ae506a
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/compassconnection.go
@@ -0,0 +1,103 @@
+package init
+
+import (
+ "context"
+ "github.com/avast/retry-go"
+ "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/apis/compass/v1alpha1"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/pkg/errors"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+type compassConnectionCRConfiguration struct {
+ compassConnectionInterface CompassConnectionInterface
+}
+
+const (
+ ConnectionCRName = "compass-connection"
+ ConnectionBackupCRName = "compass-connection-backup"
+)
+
+//go:generate mockery --name=CompassConnectionInterface
+type CompassConnectionInterface interface {
+ Get(ctx context.Context, name string, opts v1.GetOptions) (*v1alpha1.CompassConnection, error)
+ Create(ctx context.Context, compassConnection *v1alpha1.CompassConnection, opts v1.CreateOptions) (*v1alpha1.CompassConnection, error)
+ Update(ctx context.Context, compassConnection *v1alpha1.CompassConnection, opts v1.UpdateOptions) (*v1alpha1.CompassConnection, error)
+ Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
+}
+
+func NewCompassConnectionCRConfiguration(compassConnectionInterface CompassConnectionInterface) compassConnectionCRConfiguration {
+ return compassConnectionCRConfiguration{
+ compassConnectionInterface: compassConnectionInterface,
+ }
+}
+
+func (cc compassConnectionCRConfiguration) Do() (types.RollbackFunc, error) {
+ return newRollbackFunc(), nil
+}
+func (cc compassConnectionCRConfiguration) backup() (types.RollbackFunc, error) {
+ compassConnectionCR, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionCRName, meta.GetOptions{})
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to get Compass Connection CR")
+ }
+
+ compassConnectionCR.ResourceVersion = ""
+
+ compassConnectionCRBackup := compassConnectionCR.DeepCopy()
+ compassConnectionCRBackup.ObjectMeta.Name = "compass-connection-backup"
+ _, err = cc.compassConnectionInterface.Create(context.TODO(), compassConnectionCRBackup, meta.CreateOptions{})
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to create Compass Connection CR")
+ }
+
+ rollbackFunc := func() error {
+ return retry.Do(func() error {
+ err = cc.compassConnectionInterface.Delete(context.TODO(), "compass-connection-backup", meta.DeleteOptions{})
+ if err != nil {
+ if k8serrors.IsNotFound(err) {
+ return nil
+ }
+ return errors.Wrap(err, "failed to delete Compass Connection CR")
+ }
+
+ return nil
+ })
+ }
+
+ return rollbackFunc, nil
+}
+
+func (cc compassConnectionCRConfiguration) delete() (types.RollbackFunc, error) {
+ err := cc.compassConnectionInterface.Delete(context.TODO(), ConnectionCRName, meta.DeleteOptions{})
+
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to delete Compass Connection CR")
+ }
+
+ rollbackFunc := func() error {
+ return retry.Do(func() error {
+ restoredCompassConnection, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionCRName, meta.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ compassConnectionCRBackup, err := cc.compassConnectionInterface.Get(context.TODO(), ConnectionBackupCRName, meta.GetOptions{})
+ if err != nil {
+ return err
+ }
+
+ restoredCompassConnection.Spec = compassConnectionCRBackup.Spec
+ restoredCompassConnection.Status = compassConnectionCRBackup.Status
+
+ _, err = cc.compassConnectionInterface.Update(context.TODO(), restoredCompassConnection, meta.UpdateOptions{})
+ if err != nil {
+ return errors.Wrap(err, "failed to update Compass Connection CR")
+ }
+ return err
+ })
+ }
+
+ return rollbackFunc, nil
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go b/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go
new file mode 100644
index 00000000..36ec6ee1
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/compassconnection_test.go
@@ -0,0 +1,139 @@
+package init
+
+import (
+ "context"
+ "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/apis/compass/v1alpha1"
+ "github.com/kyma-project/kyma/components/compass-runtime-agent/pkg/client/clientset/versioned/fake"
+ "github.com/stretchr/testify/require"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "testing"
+)
+
+func TestCompassConnectionConfigurator(t *testing.T) {
+ t.Run("should delete CompassConnection CR and restore it when RollbackFunction is called", func(t *testing.T) {
+ // given
+ compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections()
+ compassConnection := &v1alpha1.CompassConnection{
+ ObjectMeta: meta.ObjectMeta{
+ Name: ConnectionCRName,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "CompassConnection",
+ APIVersion: "v1alpha",
+ },
+ }
+
+ _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ // when
+ configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake)
+ rollbackFunc, err := configurator.Do()
+
+ // then
+ require.NoError(t, err)
+ _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection", meta.GetOptions{})
+ require.Error(t, err)
+ require.True(t, k8serrors.IsNotFound(err))
+
+ _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection-backup", meta.GetOptions{})
+ require.NoError(t, err)
+
+ _, err = compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{})
+ require.NoError(t, err)
+ // when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ _, err = compassConnectionCRFake.Get(context.TODO(), "compass-connection", meta.GetOptions{})
+ require.NoError(t, err)
+ })
+
+ t.Run("should fail when CompassConnection CR doesn't exist", func(t *testing.T) {
+ // given
+ compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections()
+
+ // when
+ configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake)
+ _, err := configurator.Do()
+
+ // then
+ require.Error(t, err)
+ })
+
+ t.Run("should fail when CompassConnection CR backup already exist", func(t *testing.T) {
+ // given
+ compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections()
+ compassConnection := &v1alpha1.CompassConnection{
+ ObjectMeta: meta.ObjectMeta{
+ Name: ConnectionCRName,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "CompassConnection",
+ APIVersion: "v1alpha",
+ },
+ }
+
+ compassConnectionBackup := &v1alpha1.CompassConnection{
+ ObjectMeta: meta.ObjectMeta{
+ Name: ConnectionBackupCRName,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "CompassConnection",
+ APIVersion: "v1alpha",
+ },
+ }
+
+ _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ _, err = compassConnectionCRFake.Create(context.TODO(), compassConnectionBackup, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ // when
+ configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake)
+ rollbackFunc, err := configurator.Do()
+
+ // then
+ require.Nil(t, rollbackFunc)
+ require.Error(t, err)
+ })
+
+ t.Run("rollback function should fail when CompassConnection CR backup doesn't exist", func(t *testing.T) {
+ // given
+ compassConnectionCRFake := fake.NewSimpleClientset().CompassV1alpha1().CompassConnections()
+ compassConnection := &v1alpha1.CompassConnection{
+ ObjectMeta: meta.ObjectMeta{
+ Name: ConnectionCRName,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "CompassConnection",
+ APIVersion: "v1alpha",
+ },
+ }
+
+ _, err := compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ // when
+ configurator := NewCompassConnectionCRConfiguration(compassConnectionCRFake)
+ rollbackFunc, err := configurator.Do()
+
+ // then
+ require.NoError(t, err)
+
+ // when
+ _, err = compassConnectionCRFake.Create(context.TODO(), compassConnection, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ err = compassConnectionCRFake.Delete(context.TODO(), ConnectionBackupCRName, meta.DeleteOptions{})
+ require.NoError(t, err)
+
+ err = rollbackFunc()
+
+ // then
+ require.Error(t, err)
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go b/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go
new file mode 100644
index 00000000..eaa6b04a
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/configurationsecret.go
@@ -0,0 +1,83 @@
+package init
+
+import (
+ "context"
+ "github.com/avast/retry-go"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/pkg/errors"
+ v1 "k8s.io/api/core/v1"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ "time"
+)
+
+const (
+ connectorURLConfigKey = "CONNECTOR_URL"
+ tokenConfigKey = "TOKEN"
+ runtimeIdConfigKey = "RUNTIME_ID"
+ tenantConfigKey = "TENANT"
+)
+
+type configurationSecretConfigurator struct {
+ kubernetesInterface kubernetes.Interface
+}
+
+func NewConfigurationSecretConfigurator(kubernetesInterface kubernetes.Interface) configurationSecretConfigurator {
+ return configurationSecretConfigurator{
+ kubernetesInterface: kubernetesInterface,
+ }
+}
+
+func (s configurationSecretConfigurator) Do(newConfigSecretName string, config types.CompassRuntimeAgentConfig) (types.RollbackFunc, error) {
+
+ secret := v1.Secret{
+ ObjectMeta: metav1.ObjectMeta{
+ Name: newConfigSecretName,
+ Namespace: CompassSystemNamespace,
+ },
+ Data: map[string][]byte{
+ connectorURLConfigKey: []byte(config.ConnectorUrl),
+ tokenConfigKey: []byte(config.Token),
+ runtimeIdConfigKey: []byte(config.RuntimeID),
+ tenantConfigKey: []byte(config.Tenant),
+ },
+ }
+
+ err := retry.Do(func() error {
+ _, err := s.kubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.Background(), &secret, metav1.CreateOptions{})
+ if err != nil {
+ if k8serrors.IsAlreadyExists(err) {
+ return retry.Unrecoverable(err)
+ }
+ return errors.Wrap(err, "failed to create configuration secret")
+ }
+
+ return nil
+ }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second))
+
+ if err != nil {
+ return nil, err
+ }
+
+ return s.newRollbackSecretFunc(newConfigSecretName, CompassSystemNamespace), nil
+}
+
+func (s configurationSecretConfigurator) newRollbackSecretFunc(name, namespace string) types.RollbackFunc {
+ return func() error {
+ return deleteSecretWithRetry(s.kubernetesInterface, name, namespace)
+ }
+}
+
+func deleteSecretWithRetry(kubernetesInterface kubernetes.Interface, name, namespace string) error {
+ return retry.Do(func() error {
+ err := kubernetesInterface.CoreV1().Secrets(namespace).Delete(context.Background(), name, metav1.DeleteOptions{})
+ if err != nil {
+ if k8serrors.IsNotFound(err) {
+ return nil
+ }
+ }
+
+ return errors.Wrap(err, "failed to delete secret")
+ }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second))
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go b/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go
new file mode 100644
index 00000000..b3fd06fb
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/configurationsecret_test.go
@@ -0,0 +1,73 @@
+package init
+
+import (
+ "context"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/stretchr/testify/require"
+ k8serrors "k8s.io/apimachinery/pkg/api/errors"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/fake"
+ "testing"
+)
+
+func TestConfigurationSecret(t *testing.T) {
+ t.Run("should create configuration secret", func(t *testing.T) {
+ // given
+ fakeKubernetesInterface := fake.NewSimpleClientset()
+ secretConfigurator := NewConfigurationSecretConfigurator(fakeKubernetesInterface)
+ connectorURL := "www.example.com"
+ runtimeID := "runtimeID"
+ token := "token"
+ tenant := "tenant"
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: connectorURL,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: tenant,
+ }
+ secretName := "config"
+
+ // when
+ rollbackFunc, err := secretConfigurator.Do(secretName, config)
+ require.NotNil(t, rollbackFunc)
+ require.NoError(t, err)
+
+ // then
+ secret, err := fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Get(context.TODO(), secretName, meta.GetOptions{})
+ require.NoError(t, err)
+
+ require.Equal(t, connectorURL, string(secret.Data[connectorURLConfigKey]))
+ require.Equal(t, token, string(secret.Data[tokenConfigKey]))
+ require.Equal(t, runtimeID, string(secret.Data[runtimeIdConfigKey]))
+ require.Equal(t, tenant, string(secret.Data[tenantConfigKey]))
+
+ // when
+ err = rollbackFunc()
+ require.NoError(t, err)
+
+ _, err = fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Get(context.TODO(), secretName, meta.GetOptions{})
+ require.Error(t, err)
+ require.True(t, k8serrors.IsNotFound(err))
+ })
+
+ t.Run("should return error when failed to create secret", func(t *testing.T) {
+ // given
+ fakeKubernetesInterface := fake.NewSimpleClientset()
+ secretConfigurator := NewConfigurationSecretConfigurator(fakeKubernetesInterface)
+
+ config := types.CompassRuntimeAgentConfig{}
+ secretName := "config"
+
+ // when
+ secret := createSecret(secretName, CompassSystemNamespace)
+ _, err := fakeKubernetesInterface.CoreV1().Secrets(CompassSystemNamespace).Create(context.Background(), secret, meta.CreateOptions{})
+ require.NoError(t, err)
+
+ rollbackFunc, err := secretConfigurator.Do(secretName, config)
+
+ // then
+ require.Nil(t, rollbackFunc)
+ require.Error(t, err)
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/deployment.go b/tests/test/compass-runtime-agent/testkit/init/deployment.go
new file mode 100644
index 00000000..ffe11af0
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/deployment.go
@@ -0,0 +1,160 @@
+package init
+
+import (
+ "context"
+ "fmt"
+ "github.com/avast/retry-go"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/pkg/errors"
+ v12 "k8s.io/api/apps/v1"
+ v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes"
+ v13 "k8s.io/client-go/kubernetes/typed/apps/v1"
+ "time"
+)
+
+const (
+ CRAContainerNumber = 0
+ ConfigurationSecretEnvName = "APP_AGENT_CONFIGURATION_SECRET"
+ CASecretEnvName = "APP_CA_CERTIFICATES_SECRET"
+ ClusterCertSecretEnvName = "APP_CLUSTER_CERTIFICATES_SECRET"
+ ControllerSyncPeriodEnvTime = "APP_CONTROLLER_SYNC_PERIOD"
+)
+
+type deploymentConfiguration struct {
+ kubernetesInterface kubernetes.Interface
+ deploymentName string
+ namespaceName string
+}
+
+func NewDeploymentConfiguration(kubernetesInterface kubernetes.Interface, deploymentName, namespaceName string) deploymentConfiguration {
+ return deploymentConfiguration{
+ kubernetesInterface: kubernetesInterface,
+ deploymentName: deploymentName,
+ namespaceName: namespaceName,
+ }
+}
+
+func (dc deploymentConfiguration) Do(newCANamespacedSecretName, newClusterNamespacedCertSecretName, newConfigNamespacedSecretName, newControllerSyncPeriodTime string) (types.RollbackFunc, error) {
+ deploymentInterface := dc.kubernetesInterface.AppsV1().Deployments(dc.namespaceName)
+
+ deployment, err := retryGetDeployment(dc.deploymentName, deploymentInterface)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(deployment.Spec.Template.Spec.Containers) < 1 {
+ return nil, fmt.Errorf("no containers found in %s/%s deployment", "kyma-system", dc.deploymentName)
+ }
+
+ previousConfigSecretNamespacedName, found := replaceEnvValue(deployment, ConfigurationSecretEnvName, newConfigNamespacedSecretName)
+ if !found {
+ return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ConfigurationSecretEnvName, dc.deploymentName)
+ }
+
+ previousCASecretNamespacedName, found := replaceEnvValue(deployment, CASecretEnvName, newCANamespacedSecretName)
+ if !found {
+ return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", CASecretEnvName, dc.deploymentName)
+ }
+
+ previousCertSecretNamespacedName, found := replaceEnvValue(deployment, ClusterCertSecretEnvName, newClusterNamespacedCertSecretName)
+ if !found {
+ return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ClusterCertSecretEnvName, dc.deploymentName)
+ }
+
+ previousControllerSyncPeriodTime, found := replaceEnvValue(deployment, ControllerSyncPeriodEnvTime, newControllerSyncPeriodTime)
+ if !found {
+ return nil, fmt.Errorf("environment variable '%s' not found in %s deployment", ControllerSyncPeriodEnvTime, dc.deploymentName)
+ }
+
+ err = retryUpdateDeployment(deployment, deploymentInterface)
+ if err != nil {
+ return nil, err
+ }
+ rollbackDeploymentFunc := newRollbackDeploymentFunc(dc.deploymentName, previousConfigSecretNamespacedName, previousCASecretNamespacedName, previousCertSecretNamespacedName, previousControllerSyncPeriodTime, deploymentInterface)
+
+ err = waitForRollout(dc.deploymentName, deploymentInterface)
+
+ return rollbackDeploymentFunc, err
+}
+
+func newRollbackDeploymentFunc(name, previousConfigSecretNamespacedName, previousCASecretNamespacedName, previousCertSecretNamespacedName, previousControllerSyncPeriodTime string, deploymentInterface v13.DeploymentInterface) types.RollbackFunc {
+ return func() error {
+ deployment, err := retryGetDeployment(name, deploymentInterface)
+ if err != nil {
+ return err
+ }
+
+ _, found := replaceEnvValue(deployment, ConfigurationSecretEnvName, previousConfigSecretNamespacedName)
+ if !found {
+ return fmt.Errorf("environment variable '%s' not found in %s deployment", ConfigurationSecretEnvName, name)
+ }
+
+ _, found = replaceEnvValue(deployment, CASecretEnvName, previousCASecretNamespacedName)
+ if !found {
+ return fmt.Errorf("environment variable '%s' not found in %s deployment", CASecretEnvName, name)
+ }
+
+ _, found = replaceEnvValue(deployment, ClusterCertSecretEnvName, previousCertSecretNamespacedName)
+ if !found {
+ return fmt.Errorf("environment variable '%s' not found in %s deployment", ClusterCertSecretEnvName, name)
+ }
+
+ _, found = replaceEnvValue(deployment, ControllerSyncPeriodEnvTime, previousControllerSyncPeriodTime)
+ if !found {
+ return fmt.Errorf("environment variable '%s' not found in %s deployment", ControllerSyncPeriodEnvTime, name)
+ }
+
+ return retryUpdateDeployment(deployment, deploymentInterface)
+ }
+}
+
+func replaceEnvValue(deployment *v12.Deployment, name, newValue string) (string, bool) {
+ envs := deployment.Spec.Template.Spec.Containers[CRAContainerNumber].Env
+ for i := range envs {
+ if envs[i].Name == name {
+ previousValue := envs[i].Value
+ envs[i].Value = newValue
+ deployment.Spec.Template.Spec.Containers[CRAContainerNumber].Env = envs
+
+ return previousValue, true
+ }
+ }
+
+ return "", false
+}
+
+func retryGetDeployment(name string, deploymentInterface v13.DeploymentInterface) (*v12.Deployment, error) {
+ var deployment *v12.Deployment
+
+ err := retry.Do(func() error {
+ var err error
+ deployment, err = deploymentInterface.Get(context.TODO(), name, v1.GetOptions{})
+ if err != nil {
+ return errors.Wrap(err, "failed to get Compass Runtime Agent deployment")
+ }
+ return nil
+ }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second))
+
+ return deployment, err
+}
+
+func retryUpdateDeployment(deployment *v12.Deployment, deploymentInterface v13.DeploymentInterface) error {
+ return retry.Do(func() error {
+ _, err := deploymentInterface.Update(context.TODO(), deployment, v1.UpdateOptions{})
+ return errors.Wrap(err, "failed to update Compass Runtime Agent deployment")
+ }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second))
+}
+
+func waitForRollout(name string, deploymentInterface v13.DeploymentInterface) error {
+ return retry.Do(func() error {
+ deployment, err := deploymentInterface.Get(context.TODO(), name, v1.GetOptions{})
+ if err != nil {
+ return errors.Wrap(err, "failed to get Compass Runtime Agent deployment")
+ }
+ if deployment.Status.AvailableReplicas == 0 || deployment.Status.UnavailableReplicas != 0 {
+ return fmt.Errorf("deployment %s is not yet ready", name)
+ }
+ return nil
+ }, retry.Attempts(RetryAttempts), retry.Delay(RetrySeconds*time.Second))
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/init.go b/tests/test/compass-runtime-agent/testkit/init/init.go
new file mode 100644
index 00000000..94849b3c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/init.go
@@ -0,0 +1,141 @@
+package init
+
+import (
+ "fmt"
+
+ "github.com/hashicorp/go-multierror"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ log "github.com/sirupsen/logrus"
+)
+
+const (
+ CompassSystemNamespace = "kyma-system"
+ IstioSystemNamespace = "istio-system"
+ CompassRuntimeAgentDeployment = "compass-runtime-agent"
+ NewCompassRuntimeConfigName = "compass-agent-configuration"
+ NewCACertSecretName = "ca-cert-test"
+ NewClientCertSecretName = "client-cert-test"
+ NewControllerSyncPeriodTime = "15s"
+ RetryAttempts = 6
+ RetrySeconds = 5
+)
+
+type CompassRuntimeAgentConfigurator interface {
+ Do(runtimeName, formationName string) (types.RollbackFunc, error)
+}
+
+type Configurator interface {
+ Configure(runtimeName, formationName string) (types.RollbackFunc, error)
+}
+
+type compassRuntimeAgentConfigurator struct {
+ compassConfigurator types.CompassConfigurator
+ certificateSecretConfigurator types.CertificateSecretConfigurator
+ configurationSecretConfigurator types.ConfigurationSecretConfigurator
+ compassConnectionConfigurator types.CompassConnectionConfigurator
+ deploymentConfigurator types.DeploymentConfigurator
+ testNamespace string
+}
+
+func NewCompassRuntimeAgentConfigurator(compassConfigurator types.CompassConfigurator,
+ certificateSecretConfigurator types.CertificateSecretConfigurator,
+ configurationSecretConfigurator types.ConfigurationSecretConfigurator,
+ compassConnectionConfigurator types.CompassConnectionConfigurator,
+ deploymentConfigurator types.DeploymentConfigurator,
+ testNamespace string) CompassRuntimeAgentConfigurator {
+ return compassRuntimeAgentConfigurator{
+ compassConfigurator: compassConfigurator,
+ certificateSecretConfigurator: certificateSecretConfigurator,
+ configurationSecretConfigurator: configurationSecretConfigurator,
+ compassConnectionConfigurator: compassConnectionConfigurator,
+ deploymentConfigurator: deploymentConfigurator,
+ testNamespace: testNamespace,
+ }
+}
+
+func (crc compassRuntimeAgentConfigurator) Do(runtimeName, formationName string) (types.RollbackFunc, error) {
+ log.Info("Configuring Compass")
+ compassRuntimeAgentConfig, compassConfiguratorRollbackFunc, err := crc.compassConfigurator.Do(runtimeName, formationName)
+ if err != nil {
+ return nil, crc.rollbackOnError(err,
+ compassConfiguratorRollbackFunc)
+ }
+
+ log.Info("Configuring certificate secrets")
+ certificateSecretsRollbackFunc, err := crc.certificateSecretConfigurator.Do(NewCACertSecretName, NewClientCertSecretName)
+ if err != nil {
+ return nil, crc.rollbackOnError(err,
+ compassConfiguratorRollbackFunc,
+ certificateSecretsRollbackFunc)
+ }
+
+ log.Info("Preparing Compass Runtime Agent configuration secret")
+ configurationSecretRollbackFunc, err := crc.configurationSecretConfigurator.Do(NewCompassRuntimeConfigName, compassRuntimeAgentConfig)
+ if err != nil {
+ return nil, crc.rollbackOnError(err,
+ compassConfiguratorRollbackFunc,
+ certificateSecretsRollbackFunc,
+ configurationSecretRollbackFunc)
+ }
+
+ newCACertNamespacedSecretName := fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName)
+ newClientCertNamespacedSecretName := fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName)
+ newCompassRuntimeNamespacedSecretConfigName := fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName)
+ newControllerSyncPeriodTime := NewControllerSyncPeriodTime
+
+ log.Info("Preparing Compass Runtime Agent configuration secret")
+ deploymentRollbackFunc, err := crc.deploymentConfigurator.Do(newCACertNamespacedSecretName,
+ newClientCertNamespacedSecretName,
+ newCompassRuntimeNamespacedSecretConfigName, newControllerSyncPeriodTime)
+ if err != nil {
+ return nil, crc.rollbackOnError(err,
+ compassConfiguratorRollbackFunc,
+ certificateSecretsRollbackFunc,
+ configurationSecretRollbackFunc,
+ deploymentRollbackFunc)
+ }
+
+ compassConnectionRollbackFunc, err := crc.compassConnectionConfigurator.Do()
+ if err != nil {
+ return nil, crc.rollbackOnError(err,
+ compassConfiguratorRollbackFunc,
+ certificateSecretsRollbackFunc,
+ configurationSecretRollbackFunc,
+ deploymentRollbackFunc,
+ compassConnectionRollbackFunc)
+ }
+
+ return newRollbackFunc(compassConfiguratorRollbackFunc,
+ certificateSecretsRollbackFunc,
+ configurationSecretRollbackFunc,
+ deploymentRollbackFunc,
+ compassConnectionRollbackFunc), nil
+}
+
+func (crc compassRuntimeAgentConfigurator) rollbackOnError(initialErr error, rollbackFunctions ...types.RollbackFunc) error {
+ var result *multierror.Error
+ result = multierror.Append(result, initialErr)
+
+ err := newRollbackFunc(rollbackFunctions...)()
+ if err != nil {
+ result = multierror.Append(result, err)
+ }
+
+ return result.ErrorOrNil()
+}
+
+func newRollbackFunc(rollbackFunctions ...types.RollbackFunc) types.RollbackFunc {
+ var result *multierror.Error
+
+ return func() error {
+ for _, f := range rollbackFunctions {
+ if f != nil {
+ if err := f(); err != nil {
+ result = multierror.Append(result, err)
+ }
+ }
+ }
+
+ return result.ErrorOrNil()
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/init_test.go b/tests/test/compass-runtime-agent/testkit/init/init_test.go
new file mode 100644
index 00000000..9078c01e
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/init_test.go
@@ -0,0 +1,232 @@
+package init
+
+import (
+ "fmt"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types/mocks"
+ "github.com/pkg/errors"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "testing"
+)
+
+func TestCompassRuntimeAgentInit(t *testing.T) {
+ runtimeName := "newRuntime"
+ runtimeID := "runtimeID"
+ token := "token"
+ connectorURL := "www.someurl.com"
+ tenant := "tenant"
+ formationName := "newFormation"
+
+ t.Run("should succeed and return rollback function", func(t *testing.T) {
+ // given
+ compassConfiguratorMock := &mocks.CompassConfigurator{}
+ certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{}
+ configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{}
+ compassConnectionConfiguratorMock := &mocks.CompassConnectionConfigurator{}
+ deploymentConfiguratorMock := &mocks.DeploymentConfigurator{}
+
+ compassConfiguratorRollbackFunc := RollbackFuncTest{}
+ certificateSecretsRollbackFunc := RollbackFuncTest{}
+ configurationSecretRollbackFunc := RollbackFuncTest{}
+ compassConnectionRollbackFunc := RollbackFuncTest{}
+ deploymentRollbackFunc := RollbackFuncTest{}
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: connectorURL,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: tenant,
+ }
+
+ compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil)
+ certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil)
+ configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil)
+ compassConnectionConfiguratorMock.On("Do").Return(compassConnectionRollbackFunc.Func(), nil)
+ deploymentConfiguratorMock.On("Do",
+ fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName),
+ fmt.Sprintf("%s", NewControllerSyncPeriodTime)).
+ Return(deploymentRollbackFunc.Func(), nil)
+
+ configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, compassConnectionConfiguratorMock, deploymentConfiguratorMock, "tenant")
+
+ // when
+ rollbackFunc, err := configurator.Do(runtimeName, formationName)
+
+ // then
+ require.NoError(t, err)
+ certificateSecretConfiguratorMock.AssertExpectations(t)
+ compassConnectionConfiguratorMock.AssertExpectations(t)
+ deploymentConfiguratorMock.AssertExpectations(t)
+
+ //when
+ err = rollbackFunc()
+
+ // then
+ require.NoError(t, err)
+ require.True(t, compassConfiguratorRollbackFunc.invoked)
+ require.True(t, certificateSecretsRollbackFunc.invoked)
+ require.True(t, configurationSecretRollbackFunc.invoked)
+ require.True(t, compassConnectionRollbackFunc.invoked)
+ require.True(t, deploymentRollbackFunc.invoked)
+ })
+
+ t.Run("should fail if failed to register runtime", func(t *testing.T) {
+ // given
+ compassConfiguratorMock := &mocks.CompassConfigurator{}
+ compassConfiguratorRollbackFunc := RollbackFuncTest{}
+
+ compassConfiguratorMock.On("Do", runtimeName, formationName).Return(types.CompassRuntimeAgentConfig{}, compassConfiguratorRollbackFunc.Func(), errors.New("some error"))
+
+ configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, nil, nil, nil, nil, "tenant")
+
+ // when
+ rollbackFunc, err := configurator.Do(runtimeName, formationName)
+
+ // then
+ require.Error(t, err)
+ require.Nil(t, rollbackFunc)
+ assert.True(t, compassConfiguratorRollbackFunc.invoked)
+ })
+
+ t.Run("should fail if failed to create configuration secret", func(t *testing.T) {
+ // given
+ compassConfiguratorMock := &mocks.CompassConfigurator{}
+ certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{}
+ configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{}
+
+ compassConfiguratorRollbackFunc := RollbackFuncTest{}
+ certificateSecretsRollbackFunc := RollbackFuncTest{}
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: connectorURL,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: tenant,
+ }
+
+ compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil)
+ certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil)
+ configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(nil, errors.New("some error"))
+
+ configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, nil, nil, "tenant")
+
+ // when
+ rollbackFunc, err := configurator.Do(runtimeName, formationName)
+
+ // then
+ require.Error(t, err)
+ require.Nil(t, rollbackFunc)
+ compassConfiguratorMock.AssertExpectations(t)
+ certificateSecretConfiguratorMock.AssertExpectations(t)
+ certificateSecretConfiguratorMock.AssertExpectations(t)
+ require.True(t, compassConfiguratorRollbackFunc.invoked)
+ require.True(t, certificateSecretsRollbackFunc.invoked)
+ })
+
+ t.Run("should fail if failed to modify deployment", func(t *testing.T) {
+ // given
+ compassConfiguratorMock := &mocks.CompassConfigurator{}
+ certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{}
+ configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{}
+ deploymentConfiguratorMock := &mocks.DeploymentConfigurator{}
+
+ compassConfiguratorRollbackFunc := RollbackFuncTest{}
+ certificateSecretsRollbackFunc := RollbackFuncTest{}
+ configurationSecretRollbackFunc := RollbackFuncTest{}
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: connectorURL,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: tenant,
+ }
+
+ compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil)
+ certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil)
+ configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil)
+ deploymentConfiguratorMock.On("Do",
+ fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName),
+ fmt.Sprintf("%s", NewControllerSyncPeriodTime)).
+ Return(nil, errors.New("some error"))
+
+ configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, nil, deploymentConfiguratorMock, "tenant")
+
+ // when
+ rollbackFunc, err := configurator.Do(runtimeName, formationName)
+
+ // then
+ require.Error(t, err)
+ require.Nil(t, rollbackFunc)
+ certificateSecretConfiguratorMock.AssertExpectations(t)
+ deploymentConfiguratorMock.AssertExpectations(t)
+ require.True(t, compassConfiguratorRollbackFunc.invoked)
+ require.True(t, certificateSecretsRollbackFunc.invoked)
+ require.True(t, configurationSecretRollbackFunc.invoked)
+ })
+
+ t.Run("should fail if failed to configure Compass Connection CR", func(t *testing.T) {
+ // given
+ compassConfiguratorMock := &mocks.CompassConfigurator{}
+ certificateSecretConfiguratorMock := &mocks.CertificateSecretConfigurator{}
+ configurationSecretConfiguratorMock := &mocks.ConfigurationSecretConfigurator{}
+ compassConnectionConfiguratorMock := &mocks.CompassConnectionConfigurator{}
+ deploymentConfiguratorMock := &mocks.DeploymentConfigurator{}
+
+ compassConfiguratorRollbackFunc := RollbackFuncTest{}
+ certificateSecretsRollbackFunc := RollbackFuncTest{}
+ configurationSecretRollbackFunc := RollbackFuncTest{}
+ compassConnectionRollbackFunc := RollbackFuncTest{}
+ deploymentRollbackFunc := RollbackFuncTest{}
+
+ config := types.CompassRuntimeAgentConfig{
+ ConnectorUrl: connectorURL,
+ RuntimeID: runtimeID,
+ Token: token,
+ Tenant: tenant,
+ }
+
+ compassConfiguratorMock.On("Do", runtimeName, formationName).Return(config, compassConfiguratorRollbackFunc.Func(), nil)
+ certificateSecretConfiguratorMock.On("Do", NewCACertSecretName, NewClientCertSecretName).Return(certificateSecretsRollbackFunc.Func(), nil)
+ configurationSecretConfiguratorMock.On("Do", NewCompassRuntimeConfigName, config).Return(configurationSecretRollbackFunc.Func(), nil)
+ compassConnectionConfiguratorMock.On("Do").Return(compassConnectionRollbackFunc.Func(), errors.New("some error"))
+ deploymentConfiguratorMock.On("Do",
+ fmt.Sprintf("%s/%s", IstioSystemNamespace, NewCACertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewClientCertSecretName),
+ fmt.Sprintf("%s/%s", CompassSystemNamespace, NewCompassRuntimeConfigName),
+ fmt.Sprintf("%s", NewControllerSyncPeriodTime)).
+ Return(deploymentRollbackFunc.Func(), nil)
+
+ configurator := NewCompassRuntimeAgentConfigurator(compassConfiguratorMock, certificateSecretConfiguratorMock, configurationSecretConfiguratorMock, compassConnectionConfiguratorMock, deploymentConfiguratorMock, "tenant")
+
+ // when
+ rollbackFunc, err := configurator.Do(runtimeName, formationName)
+
+ // then
+ require.Error(t, err)
+ require.Nil(t, rollbackFunc)
+ certificateSecretConfiguratorMock.AssertExpectations(t)
+ compassConnectionConfiguratorMock.AssertExpectations(t)
+ deploymentConfiguratorMock.AssertExpectations(t)
+ require.True(t, compassConfiguratorRollbackFunc.invoked)
+ require.True(t, certificateSecretsRollbackFunc.invoked)
+ require.True(t, configurationSecretRollbackFunc.invoked)
+ //require.True(t, compassConnectionRollbackFunc.invoked)
+ require.True(t, deploymentRollbackFunc.invoked)
+ })
+}
+
+type RollbackFuncTest struct {
+ invoked bool
+}
+
+func (rfc *RollbackFuncTest) Func() types.RollbackFunc {
+ return func() error {
+ rfc.invoked = true
+ return nil
+ }
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go
new file mode 100644
index 00000000..3130678b
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CertificateSecretConfigurator.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// CertificateSecretConfigurator is an autogenerated mock type for the CertificateSecretConfigurator type
+type CertificateSecretConfigurator struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields: caSecretName, clusterCertSecretName
+func (_m *CertificateSecretConfigurator) Do(caSecretName string, clusterCertSecretName string) (types.RollbackFunc, error) {
+ ret := _m.Called(caSecretName, clusterCertSecretName)
+
+ var r0 types.RollbackFunc
+ if rf, ok := ret.Get(0).(func(string, string) types.RollbackFunc); ok {
+ r0 = rf(caSecretName, clusterCertSecretName)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(types.RollbackFunc)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string, string) error); ok {
+ r1 = rf(caSecretName, clusterCertSecretName)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewCertificateSecretConfigurator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewCertificateSecretConfigurator creates a new instance of CertificateSecretConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewCertificateSecretConfigurator(t mockConstructorTestingTNewCertificateSecretConfigurator) *CertificateSecretConfigurator {
+ mock := &CertificateSecretConfigurator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go
new file mode 100644
index 00000000..aa11389a
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConfigurator.go
@@ -0,0 +1,58 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// CompassConfigurator is an autogenerated mock type for the CompassConfigurator type
+type CompassConfigurator struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields: runtimeName, formationName
+func (_m *CompassConfigurator) Do(runtimeName string, formationName string) (types.CompassRuntimeAgentConfig, types.RollbackFunc, error) {
+ ret := _m.Called(runtimeName, formationName)
+
+ var r0 types.CompassRuntimeAgentConfig
+ if rf, ok := ret.Get(0).(func(string, string) types.CompassRuntimeAgentConfig); ok {
+ r0 = rf(runtimeName, formationName)
+ } else {
+ r0 = ret.Get(0).(types.CompassRuntimeAgentConfig)
+ }
+
+ var r1 types.RollbackFunc
+ if rf, ok := ret.Get(1).(func(string, string) types.RollbackFunc); ok {
+ r1 = rf(runtimeName, formationName)
+ } else {
+ if ret.Get(1) != nil {
+ r1 = ret.Get(1).(types.RollbackFunc)
+ }
+ }
+
+ var r2 error
+ if rf, ok := ret.Get(2).(func(string, string) error); ok {
+ r2 = rf(runtimeName, formationName)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+type mockConstructorTestingTNewCompassConfigurator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewCompassConfigurator creates a new instance of CompassConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewCompassConfigurator(t mockConstructorTestingTNewCompassConfigurator) *CompassConfigurator {
+ mock := &CompassConfigurator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go
new file mode 100644
index 00000000..cea552f1
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/CompassConnectionConfigurator.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// CompassConnectionConfigurator is an autogenerated mock type for the CompassConnectionConfigurator type
+type CompassConnectionConfigurator struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields:
+func (_m *CompassConnectionConfigurator) Do() (types.RollbackFunc, error) {
+ ret := _m.Called()
+
+ var r0 types.RollbackFunc
+ if rf, ok := ret.Get(0).(func() types.RollbackFunc); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(types.RollbackFunc)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func() error); ok {
+ r1 = rf()
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewCompassConnectionConfigurator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewCompassConnectionConfigurator creates a new instance of CompassConnectionConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewCompassConnectionConfigurator(t mockConstructorTestingTNewCompassConnectionConfigurator) *CompassConnectionConfigurator {
+ mock := &CompassConnectionConfigurator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go
new file mode 100644
index 00000000..33ae8a41
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/ConfigurationSecretConfigurator.go
@@ -0,0 +1,51 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// ConfigurationSecretConfigurator is an autogenerated mock type for the ConfigurationSecretConfigurator type
+type ConfigurationSecretConfigurator struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields: configurationSecretName, config
+func (_m *ConfigurationSecretConfigurator) Do(configurationSecretName string, config types.CompassRuntimeAgentConfig) (types.RollbackFunc, error) {
+ ret := _m.Called(configurationSecretName, config)
+
+ var r0 types.RollbackFunc
+ if rf, ok := ret.Get(0).(func(string, types.CompassRuntimeAgentConfig) types.RollbackFunc); ok {
+ r0 = rf(configurationSecretName, config)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(types.RollbackFunc)
+ }
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string, types.CompassRuntimeAgentConfig) error); ok {
+ r1 = rf(configurationSecretName, config)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewConfigurationSecretConfigurator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewConfigurationSecretConfigurator creates a new instance of ConfigurationSecretConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewConfigurationSecretConfigurator(t mockConstructorTestingTNewConfigurationSecretConfigurator) *ConfigurationSecretConfigurator {
+ mock := &ConfigurationSecretConfigurator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go
new file mode 100644
index 00000000..f49a52b2
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DeploymentConfigurator.go
@@ -0,0 +1,54 @@
+// Code generated by mockery v2.22.1. DO NOT EDIT.
+
+package mocks
+
+import (
+ types "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/init/types"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// DeploymentConfigurator is an autogenerated mock type for the DeploymentConfigurator type
+type DeploymentConfigurator struct {
+ mock.Mock
+}
+
+// Do provides a mock function with given fields: caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime
+func (_m *DeploymentConfigurator) Do(caSecretName string, clusterCertSecretName string, runtimeAgentConfigSecretName string, controllerSyncPeriodTime string) (types.RollbackFunc, error) {
+ ret := _m.Called(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime)
+
+ var r0 types.RollbackFunc
+ var r1 error
+ if rf, ok := ret.Get(0).(func(string, string, string, string) (types.RollbackFunc, error)); ok {
+ return rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime)
+ }
+ if rf, ok := ret.Get(0).(func(string, string, string, string) types.RollbackFunc); ok {
+ r0 = rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(types.RollbackFunc)
+ }
+ }
+
+ if rf, ok := ret.Get(1).(func(string, string, string, string) error); ok {
+ r1 = rf(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewDeploymentConfigurator interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewDeploymentConfigurator creates a new instance of DeploymentConfigurator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewDeploymentConfigurator(t mockConstructorTestingTNewDeploymentConfigurator) *DeploymentConfigurator {
+ mock := &DeploymentConfigurator{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go
new file mode 100644
index 00000000..da03b2b6
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/mocks/DirectorClient.go
@@ -0,0 +1,130 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+
+// DirectorClient is an autogenerated mock type for the DirectorClient type
+type DirectorClient struct {
+ mock.Mock
+}
+
+// AssignRuntimeToFormation provides a mock function with given fields: runtimeId, formationName
+func (_m *DirectorClient) AssignRuntimeToFormation(runtimeId string, formationName string) error {
+ ret := _m.Called(runtimeId, formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string, string) error); ok {
+ r0 = rf(runtimeId, formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// GetConnectionToken provides a mock function with given fields: runtimeID
+func (_m *DirectorClient) GetConnectionToken(runtimeID string) (string, string, error) {
+ ret := _m.Called(runtimeID)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string) string); ok {
+ r0 = rf(runtimeID)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 string
+ if rf, ok := ret.Get(1).(func(string) string); ok {
+ r1 = rf(runtimeID)
+ } else {
+ r1 = ret.Get(1).(string)
+ }
+
+ var r2 error
+ if rf, ok := ret.Get(2).(func(string) error); ok {
+ r2 = rf(runtimeID)
+ } else {
+ r2 = ret.Error(2)
+ }
+
+ return r0, r1, r2
+}
+
+// RegisterFormation provides a mock function with given fields: formationName
+func (_m *DirectorClient) RegisterFormation(formationName string) error {
+ ret := _m.Called(formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// RegisterRuntime provides a mock function with given fields: runtimeName
+func (_m *DirectorClient) RegisterRuntime(runtimeName string) (string, error) {
+ ret := _m.Called(runtimeName)
+
+ var r0 string
+ if rf, ok := ret.Get(0).(func(string) string); ok {
+ r0 = rf(runtimeName)
+ } else {
+ r0 = ret.Get(0).(string)
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func(string) error); ok {
+ r1 = rf(runtimeName)
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+// UnregisterFormation provides a mock function with given fields: formationName
+func (_m *DirectorClient) UnregisterFormation(formationName string) error {
+ ret := _m.Called(formationName)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(formationName)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+// UnregisterRuntime provides a mock function with given fields: id
+func (_m *DirectorClient) UnregisterRuntime(id string) error {
+ ret := _m.Called(id)
+
+ var r0 error
+ if rf, ok := ret.Get(0).(func(string) error); ok {
+ r0 = rf(id)
+ } else {
+ r0 = ret.Error(0)
+ }
+
+ return r0
+}
+
+type mockConstructorTestingTNewDirectorClient interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewDirectorClient creates a new instance of DirectorClient. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewDirectorClient(t mockConstructorTestingTNewDirectorClient) *DirectorClient {
+ mock := &DirectorClient{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/init/types/types.go b/tests/test/compass-runtime-agent/testkit/init/types/types.go
new file mode 100644
index 00000000..c657ad61
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/init/types/types.go
@@ -0,0 +1,45 @@
+package types
+
+type CompassRuntimeAgentConfig struct {
+ ConnectorUrl string
+ RuntimeID string
+ Token string
+ Tenant string
+}
+
+type RollbackFunc func() error
+
+//go:generate mockery --name=DirectorClient
+type DirectorClient interface {
+ RegisterRuntime(runtimeName string) (string, error)
+ RegisterFormation(formationName string) error
+ AssignRuntimeToFormation(runtimeId, formationName string) error
+ UnregisterRuntime(id string) error
+ UnregisterFormation(formationName string) error
+ GetConnectionToken(runtimeID string) (string, string, error)
+}
+
+//go:generate mockery --name=CompassConfigurator
+type CompassConfigurator interface {
+ Do(runtimeName, formationName string) (CompassRuntimeAgentConfig, RollbackFunc, error)
+}
+
+//go:generate mockery --name=DeploymentConfigurator
+type DeploymentConfigurator interface {
+ Do(caSecretName, clusterCertSecretName, runtimeAgentConfigSecretName, controllerSyncPeriodTime string) (RollbackFunc, error)
+}
+
+//go:generate mockery --name=CertificateSecretConfigurator
+type CertificateSecretConfigurator interface {
+ Do(caSecretName, clusterCertSecretName string) (RollbackFunc, error)
+}
+
+//go:generate mockery --name=ConfigurationSecretConfigurator
+type ConfigurationSecretConfigurator interface {
+ Do(configurationSecretName string, config CompassRuntimeAgentConfig) (RollbackFunc, error)
+}
+
+//go:generate mockery --name=CompassConnectionConfigurator
+type CompassConnectionConfigurator interface {
+ Do() (RollbackFunc, error)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/oauth/client.go b/tests/test/compass-runtime-agent/testkit/oauth/client.go
new file mode 100644
index 00000000..a845175d
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/oauth/client.go
@@ -0,0 +1,120 @@
+package oauth
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "net/http/httputil"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/pkg/errors"
+ log "github.com/sirupsen/logrus"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ v1 "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+//go:generate mockery --name=Client
+type Client interface {
+ GetAuthorizationToken() (Token, error)
+}
+
+type oauthClient struct {
+ httpClient *http.Client
+ secretsClient v1.SecretInterface
+ secretName string
+}
+
+func NewOauthClient(client *http.Client, secrets v1.SecretInterface, secretName string) (Client, error) {
+
+ _, err := secrets.Get(context.Background(), secretName, metav1.GetOptions{})
+
+ if err != nil {
+ return nil, fmt.Errorf("Cound not access oauthCredential secret %s", secretName)
+ }
+
+ return &oauthClient{
+ httpClient: client,
+ secretsClient: secrets,
+ secretName: secretName,
+ }, nil
+}
+
+func (c *oauthClient) GetAuthorizationToken() (Token, error) {
+ credentials, err := c.getCredentials()
+
+ if err != nil {
+ return Token{}, err
+ }
+
+ return c.getAuthorizationToken(credentials)
+}
+
+func (c *oauthClient) getCredentials() (credentials, error) {
+ secret, err := c.secretsClient.Get(context.Background(), c.secretName, metav1.GetOptions{})
+
+ if err != nil {
+ return credentials{}, err
+ }
+
+ return credentials{
+ clientID: string(secret.Data[clientIDKey]),
+ clientSecret: string(secret.Data[clientSecretKey]),
+ tokensEndpoint: string(secret.Data[tokensEndpointKey]),
+ }, nil
+}
+
+func (c *oauthClient) getAuthorizationToken(credentials credentials) (Token, error) {
+ log.Infof("Getting authorisation token for credentials to access Director from endpoint: %s", credentials.tokensEndpoint)
+
+ form := url.Values{}
+ form.Add(grantTypeFieldName, credentialsGrantType)
+ form.Add(scopeFieldName, scopes)
+
+ request, err := http.NewRequest(http.MethodPost, credentials.tokensEndpoint, strings.NewReader(form.Encode()))
+ if err != nil {
+ log.Errorf("Failed to create authorisation token request")
+ return Token{}, errors.Wrap(err, "Failed to create authorisation token request")
+ }
+
+ now := time.Now().Unix()
+
+ request.SetBasicAuth(credentials.clientID, credentials.clientSecret)
+ request.Header.Set(contentTypeHeader, contentTypeApplicationURLEncoded)
+
+ response, err := c.httpClient.Do(request)
+ if err != nil {
+ return Token{}, errors.Wrap(err, "Failed to execute http call")
+ }
+
+ defer response.Body.Close()
+
+ if response.StatusCode != http.StatusOK {
+ dump, err := httputil.DumpResponse(response, true)
+ if err != nil {
+ dump = []byte("failed to dump response body")
+ }
+ return Token{}, fmt.Errorf("Get token call returned unexpected status: %s. Response dump: %s", response.Status, string(dump))
+ }
+
+ body, err := ioutil.ReadAll(response.Body)
+ if err != nil {
+ return Token{}, fmt.Errorf("Failed to read token response body from '%s': %s", credentials.tokensEndpoint, err.Error())
+ }
+
+ tokenResponse := Token{}
+
+ err = json.Unmarshal(body, &tokenResponse)
+ if err != nil {
+ return Token{}, fmt.Errorf("failed to unmarshal token response body: %s", err.Error())
+ }
+
+ log.Infof("Successfully unmarshal response oauth token for accessing Director")
+
+ tokenResponse.Expiration += now
+
+ return tokenResponse, nil
+}
diff --git a/tests/test/compass-runtime-agent/testkit/oauth/client_test.go b/tests/test/compass-runtime-agent/testkit/oauth/client_test.go
new file mode 100644
index 00000000..5bfdb5f2
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/oauth/client_test.go
@@ -0,0 +1,109 @@
+package oauth
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "io/ioutil"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ v1 "k8s.io/api/core/v1"
+ meta "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/client-go/kubernetes/fake"
+ core "k8s.io/client-go/kubernetes/typed/core/v1"
+)
+
+const (
+ namespace = "test"
+ secretName = "oauth-compass-credentials"
+)
+
+func TestOauthClient_GetAuthorizationToken(t *testing.T) {
+ t.Run("Should return oauth token", func(t *testing.T) {
+ //given
+ credentials := credentials{
+ clientID: "12345",
+ clientSecret: "some dark and scary secret",
+ tokensEndpoint: "http://hydra:4445",
+ }
+
+ token := Token{
+ AccessToken: "12345",
+ Expiration: 1234,
+ }
+
+ client := NewTestClient(func(req *http.Request) *http.Response {
+ username, secret, ok := req.BasicAuth()
+
+ if ok && username == credentials.clientID && secret == credentials.clientSecret {
+ jsonToken, err := json.Marshal(&token)
+
+ require.NoError(t, err)
+
+ return &http.Response{
+ StatusCode: http.StatusOK,
+ Body: ioutil.NopCloser(bytes.NewReader(jsonToken)),
+ }
+ }
+ return &http.Response{
+ StatusCode: http.StatusForbidden,
+ }
+ })
+
+ coreV1 := fake.NewSimpleClientset()
+ secrets := coreV1.CoreV1().Secrets(namespace)
+
+ createFakeCredentialsSecret(t, secrets, credentials)
+
+ oauthClient, err := NewOauthClient(client, secrets, secretName)
+ require.NoError(t, err)
+
+ //when
+ responseToken, err := oauthClient.GetAuthorizationToken()
+ require.NoError(t, err)
+ token.Expiration += time.Now().Unix()
+
+ //then
+ assert.Equal(t, token.AccessToken, responseToken.AccessToken)
+ assert.Equal(t, token.Expiration, responseToken.Expiration)
+ })
+}
+
+func NewTestClient(fn RoundTripFunc) *http.Client {
+ return &http.Client{
+ Transport: fn,
+ }
+}
+
+type RoundTripFunc func(req *http.Request) *http.Response
+
+func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+ return f(req), nil
+}
+
+func createFakeCredentialsSecret(t *testing.T, secrets core.SecretInterface, credentials credentials) {
+
+ secret := &v1.Secret{
+ ObjectMeta: meta.ObjectMeta{
+ Name: secretName,
+ Namespace: namespace,
+ },
+ TypeMeta: meta.TypeMeta{
+ Kind: "Secret",
+ APIVersion: "v1",
+ },
+ Data: map[string][]byte{
+ clientIDKey: []byte(credentials.clientID),
+ clientSecretKey: []byte(credentials.clientSecret),
+ tokensEndpointKey: []byte(credentials.tokensEndpoint),
+ },
+ }
+
+ _, err := secrets.Create(context.Background(), secret, meta.CreateOptions{})
+
+ require.NoError(t, err)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go b/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go
new file mode 100644
index 00000000..3a357903
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/oauth/mocks/Client.go
@@ -0,0 +1,49 @@
+// Code generated by mockery v2.15.0. DO NOT EDIT.
+
+package mocks
+
+import (
+ oauth "github.com/kyma-project/kyma/tests/components/application-connector/test/compass-runtime-agent/testkit/oauth"
+ mock "github.com/stretchr/testify/mock"
+)
+
+// Client is an autogenerated mock type for the Client type
+type Client struct {
+ mock.Mock
+}
+
+// GetAuthorizationToken provides a mock function with given fields:
+func (_m *Client) GetAuthorizationToken() (oauth.Token, error) {
+ ret := _m.Called()
+
+ var r0 oauth.Token
+ if rf, ok := ret.Get(0).(func() oauth.Token); ok {
+ r0 = rf()
+ } else {
+ r0 = ret.Get(0).(oauth.Token)
+ }
+
+ var r1 error
+ if rf, ok := ret.Get(1).(func() error); ok {
+ r1 = rf()
+ } else {
+ r1 = ret.Error(1)
+ }
+
+ return r0, r1
+}
+
+type mockConstructorTestingTNewClient interface {
+ mock.TestingT
+ Cleanup(func())
+}
+
+// NewClient creates a new instance of Client. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
+func NewClient(t mockConstructorTestingTNewClient) *Client {
+ mock := &Client{}
+ mock.Mock.Test(t)
+
+ t.Cleanup(func() { mock.AssertExpectations(t) })
+
+ return mock
+}
diff --git a/tests/test/compass-runtime-agent/testkit/oauth/types.go b/tests/test/compass-runtime-agent/testkit/oauth/types.go
new file mode 100644
index 00000000..86fc07c2
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/oauth/types.go
@@ -0,0 +1,38 @@
+package oauth
+
+import "time"
+
+const (
+ contentTypeHeader = "Content-Type"
+ contentTypeApplicationURLEncoded = "application/x-www-form-urlencoded"
+
+ grantTypeFieldName = "grant_type"
+ credentialsGrantType = "client_credentials"
+
+ scopeFieldName = "scope"
+ scopes = "application:read application:write formation:write runtime:read runtime:write"
+
+ clientIDKey = "client_id"
+ clientSecretKey = "client_secret"
+ tokensEndpointKey = "tokens_endpoint"
+)
+
+type Token struct {
+ AccessToken string `json:"access_token"`
+ Expiration int64 `json:"expires_in"`
+}
+
+type credentials struct {
+ clientID string
+ clientSecret string
+ tokensEndpoint string
+}
+
+func (token Token) EmptyOrExpired() bool {
+ if token.AccessToken == "" {
+ return true
+ }
+
+ expiration := time.Unix(token.Expiration, 0)
+ return time.Now().After(expiration)
+}
diff --git a/tests/test/compass-runtime-agent/testkit/oauth/types_test.go b/tests/test/compass-runtime-agent/testkit/oauth/types_test.go
new file mode 100644
index 00000000..e1f8b0f7
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/oauth/types_test.go
@@ -0,0 +1,53 @@
+package oauth
+
+import (
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestToken_EmptyOrExpired(t *testing.T) {
+ t.Run("Should return true when token is empty", func(t *testing.T) {
+ //given
+ token := Token{}
+
+ //when
+ empty := token.EmptyOrExpired()
+
+ //then
+ assert.True(t, empty)
+ })
+
+ t.Run("Should return true when expired", func(t *testing.T) {
+ //given
+ time2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
+
+ token := Token{
+ AccessToken: "token",
+ Expiration: time2000,
+ }
+
+ //when
+ expired := token.EmptyOrExpired()
+
+ //then
+ assert.True(t, expired)
+ })
+
+ t.Run("Should return false when not empty or expired", func(t *testing.T) {
+ //given
+ time3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC).Unix()
+
+ token := Token{
+ AccessToken: "token",
+ Expiration: time3000,
+ }
+
+ //when
+ notExpired := token.EmptyOrExpired()
+
+ //then
+ assert.False(t, notExpired)
+ })
+}
diff --git a/tests/test/compass-runtime-agent/testkit/random/randomstring.go b/tests/test/compass-runtime-agent/testkit/random/randomstring.go
new file mode 100644
index 00000000..7fbb0075
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/random/randomstring.go
@@ -0,0 +1,25 @@
+package random
+
+import (
+ "math/rand"
+ "strings"
+ "time"
+)
+
+const charset = "abcdefghijklmnopqrstuvwxyz" +
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
+
+var seededRand *rand.Rand = rand.New(
+ rand.NewSource(time.Now().UnixNano()))
+
+func StringWithCharset(length int, charset string) string {
+ b := make([]byte, length)
+ for i := range b {
+ b[i] = charset[seededRand.Intn(len(charset))]
+ }
+ return string(b)
+}
+
+func RandomString(length int) string {
+ return strings.ToLower(StringWithCharset(length, charset))
+}
diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE
new file mode 100644
index 00000000..9b0dfaa8
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2017 Machine Box, Ltd.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md
new file mode 100644
index 00000000..fabd7c8e
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/README.md
@@ -0,0 +1,67 @@
+# graphql [![GoDoc](https://godoc.org/github.com/machinebox/graphql?status.png)](http://godoc.org/github.com/machinebox/graphql) [![Build Status](https://travis-ci.org/machinebox/graphql.svg?branch=master)](https://travis-ci.org/machinebox/graphql) [![Go Report Card](https://goreportcard.com/badge/github.com/machinebox/graphql)](https://goreportcard.com/report/github.com/machinebox/graphql)
+
+Low-level GraphQL client for Go.
+
+* Simple, familiar API
+* Respects `context.Context` timeouts and cancellation
+* Build and execute any kind of GraphQL request
+* Use strong Go types for response data
+* Use variables and upload files
+* Simple error handling
+
+## Installation
+Make sure you have a working Go environment. To install graphql, simply run:
+
+```
+$ go get github.com/machinebox/graphql
+```
+
+## Usage
+
+```go
+import "context"
+
+// create a client (safe to share across requests)
+client := graphql.NewClient("https://machinebox.io/graphql")
+
+// make a request
+req := graphql.NewRequest(`
+ query ($key: String!) {
+ items (id:$key) {
+ field1
+ field2
+ field3
+ }
+ }
+`)
+
+// set any variables
+req.Var("key", "value")
+
+// set header fields
+req.Header.Set("Cache-Control", "no-cache")
+
+// define a Context for the request
+ctx := context.Background()
+
+// run it and capture the response
+var respData ResponseStruct
+if err := client.Run(ctx, req, &respData); err != nil {
+ log.Fatal(err)
+}
+```
+
+### File Support via Multipart Form Data
+
+By default, the package will send a JSON body. To enable the sending of files, you can opt to
+use multipart form data instead using the `UseMultipartForm` option when you create your `Client`:
+
+```
+client := graphql.NewClient("https://machinebox.io/graphql", graphql.UseMultipartForm())
+```
+
+For more information, [read the godoc package documentation](http://godoc.org/github.com/machinebox/graphql).
+
+## Thanks
+
+Thanks to [Chris Broadfoot](https://github.com/broady) for design help.
diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go
new file mode 100644
index 00000000..dc005dad
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql.go
@@ -0,0 +1,354 @@
+// Package graphql provides a low level GraphQL client.
+//
+// // create a client (safe to share across requests)
+// client := graphql.NewClient("https://machinebox.io/graphql")
+//
+// // make a request
+// req := graphql.NewRequest(`
+// query ($key: String!) {
+// items (id:$key) {
+// field1
+// field2
+// field3
+// }
+// }
+// `)
+//
+// // set any variables
+// req.Var("key", "value")
+//
+// // run it and capture the response
+// var respData ResponseStruct
+// if err := client.Run(ctx, req, &respData); err != nil {
+// log.Fatal(err)
+// }
+//
+// Specify client
+//
+// To specify your own http.Client, use the WithHTTPClient option:
+// httpclient := &http.Client{}
+// client := graphql.NewClient("https://machinebox.io/graphql", graphql.WithHTTPClient(httpclient))
+package graphql
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "strings"
+ "unicode/utf8"
+
+ "github.com/pkg/errors"
+)
+
+// Client is a client for interacting with a GraphQL API.
+type Client struct {
+ endpoint string
+ httpClient *http.Client
+ useMultipartForm bool
+
+ // closeReq will close the request body immediately allowing for reuse of client
+ closeReq bool
+
+ // Log is called with various debug information.
+ // To log to standard out, use:
+ // client.Log = func(s string) { log.Println(s) }
+ Log func(s string)
+}
+
+// NewClient makes a new Client capable of making GraphQL requests.
+func NewClient(endpoint string, opts ...ClientOption) *Client {
+ c := &Client{
+ endpoint: endpoint,
+ Log: func(string) {},
+ }
+ for _, optionFunc := range opts {
+ optionFunc(c)
+ }
+ if c.httpClient == nil {
+ c.httpClient = http.DefaultClient
+ }
+ return c
+}
+
+func (c *Client) logf(format string, args ...interface{}) {
+ c.Log(fmt.Sprintf(format, args...))
+}
+
+// Run executes the query and unmarshals the response from the data field
+// into the response object.
+// Pass in a nil response object to skip response parsing.
+// If the request fails or the server returns an error, the first error
+// will be returned.
+func (c *Client) Run(ctx context.Context, req *Request, resp interface{}) error {
+ select {
+ case <-ctx.Done():
+ return ctx.Err()
+ default:
+ }
+ if len(req.files) > 0 && !c.useMultipartForm {
+ return errors.New("cannot send files with PostFields option")
+ }
+ if c.useMultipartForm {
+ return c.runWithPostFields(ctx, req, resp)
+ }
+ return c.runWithJSON(ctx, req, resp)
+}
+
+func (c *Client) runWithJSON(ctx context.Context, req *Request, resp interface{}) error {
+ var requestBody bytes.Buffer
+ requestBodyObj := struct {
+ Query string `json:"query"`
+ Variables map[string]interface{} `json:"variables"`
+ }{
+ Query: req.q,
+ Variables: req.vars,
+ }
+ if err := json.NewEncoder(&requestBody).Encode(requestBodyObj); err != nil {
+ return errors.Wrap(err, "encode body")
+ }
+ c.logf(">> variables: %v", req.vars)
+ c.logf(">> query: %s", req.q)
+ gr := &graphResponse{
+ Data: resp,
+ }
+ r, err := http.NewRequest(http.MethodPost, c.endpoint, &requestBody)
+ if err != nil {
+ return err
+ }
+ r.Close = c.closeReq
+ r.Header.Set("Content-Type", "application/json; charset=utf-8")
+ r.Header.Set("Accept", "application/json; charset=utf-8")
+ for key, values := range req.Header {
+ for _, value := range values {
+ r.Header.Add(key, value)
+ }
+ }
+ c.logf(">> headers: %v", hideHeaders(r.Header))
+ r = r.WithContext(ctx)
+ res, err := c.httpClient.Do(r)
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, res.Body); err != nil {
+ return errors.Wrap(err, "reading body")
+ }
+ c.logf("<< %s", buf.String())
+ if err := json.NewDecoder(&buf).Decode(&gr); err != nil {
+ if res.StatusCode != http.StatusOK {
+ return fmt.Errorf("graphql: server returned a non-200 status code: %v", res.StatusCode)
+ }
+ return errors.Wrap(err, "decoding response")
+ }
+ if len(gr.Errors) > 0 {
+ // return first error
+ return gr.Errors[0]
+ }
+ return nil
+}
+
+func (c *Client) runWithPostFields(ctx context.Context, req *Request, resp interface{}) error {
+ var requestBody bytes.Buffer
+ writer := multipart.NewWriter(&requestBody)
+ if err := writer.WriteField("query", req.q); err != nil {
+ return errors.Wrap(err, "write query field")
+ }
+ var variablesBuf bytes.Buffer
+ if len(req.vars) > 0 {
+ variablesField, err := writer.CreateFormField("variables")
+ if err != nil {
+ return errors.Wrap(err, "create variables field")
+ }
+ if err := json.NewEncoder(io.MultiWriter(variablesField, &variablesBuf)).Encode(req.vars); err != nil {
+ return errors.Wrap(err, "encode variables")
+ }
+ }
+ for i := range req.files {
+ part, err := writer.CreateFormFile(req.files[i].Field, req.files[i].Name)
+ if err != nil {
+ return errors.Wrap(err, "create form file")
+ }
+ if _, err := io.Copy(part, req.files[i].R); err != nil {
+ return errors.Wrap(err, "preparing file")
+ }
+ }
+ if err := writer.Close(); err != nil {
+ return errors.Wrap(err, "close writer")
+ }
+ c.logf(">> variables: %s", variablesBuf.String())
+ c.logf(">> files: %d", len(req.files))
+ c.logf(">> query: %s", req.q)
+ gr := &graphResponse{
+ Data: resp,
+ }
+ r, err := http.NewRequest(http.MethodPost, c.endpoint, &requestBody)
+ if err != nil {
+ return err
+ }
+ r.Close = c.closeReq
+ r.Header.Set("Content-Type", writer.FormDataContentType())
+ r.Header.Set("Accept", "application/json; charset=utf-8")
+ for key, values := range req.Header {
+ for _, value := range values {
+ r.Header.Add(key, value)
+ }
+ }
+ c.logf(">> headers: %v", hideHeaders(r.Header))
+ r = r.WithContext(ctx)
+ res, err := c.httpClient.Do(r)
+ if err != nil {
+ return err
+ }
+ defer res.Body.Close()
+ var buf bytes.Buffer
+ if _, err := io.Copy(&buf, res.Body); err != nil {
+ return errors.Wrap(err, "reading body")
+ }
+ c.logf("<< %s", buf.String())
+ if err := json.NewDecoder(&buf).Decode(&gr); err != nil {
+ if res.StatusCode != http.StatusOK {
+ return fmt.Errorf("graphql: server returned a non-200 status code: %v", res.StatusCode)
+ }
+ return errors.Wrap(err, "decoding response")
+ }
+ if len(gr.Errors) > 0 {
+ // return first error
+ return gr.Errors[0]
+ }
+ return nil
+}
+
+// WithHTTPClient specifies the underlying http.Client to use when
+// making requests.
+// NewClient(endpoint, WithHTTPClient(specificHTTPClient))
+func WithHTTPClient(httpclient *http.Client) ClientOption {
+ return func(client *Client) {
+ client.httpClient = httpclient
+ }
+}
+
+// UseMultipartForm uses multipart/form-data and activates support for
+// files.
+func UseMultipartForm() ClientOption {
+ return func(client *Client) {
+ client.useMultipartForm = true
+ }
+}
+
+// ImmediatelyCloseReqBody will close the req body immediately after each request body is ready
+func ImmediatelyCloseReqBody() ClientOption {
+ return func(client *Client) {
+ client.closeReq = true
+ }
+}
+
+// ClientOption are functions that are passed into NewClient to
+// modify the behaviour of the Client.
+type ClientOption func(*Client)
+
+type ExtendedError interface {
+ Error() string
+ Extensions() map[string]interface{}
+}
+
+type graphErr struct {
+ Message string `json:"message,omitempty"`
+ ErrorExtensions map[string]interface{} `json:"extensions,omitempty"`
+}
+
+func (e graphErr) Error() string {
+ return "graphql: " + e.Message
+}
+
+func (e graphErr) Extensions() map[string]interface{} {
+ return e.ErrorExtensions
+}
+
+type graphResponse struct {
+ Data interface{}
+ Errors []graphErr
+}
+
+// Request is a GraphQL request.
+type Request struct {
+ q string
+ vars map[string]interface{}
+ files []File
+
+ // Header represent any request headers that will be set
+ // when the request is made.
+ Header http.Header
+}
+
+// NewRequest makes a new Request with the specified string.
+func NewRequest(q string) *Request {
+ req := &Request{
+ q: q,
+ Header: make(map[string][]string),
+ }
+ return req
+}
+
+// Var sets a variable.
+func (req *Request) Var(key string, value interface{}) {
+ if req.vars == nil {
+ req.vars = make(map[string]interface{})
+ }
+ req.vars[key] = value
+}
+
+// Vars gets the variables for this Request.
+func (req *Request) Vars() map[string]interface{} {
+ return req.vars
+}
+
+// Files gets the files in this request.
+func (req *Request) Files() []File {
+ return req.files
+}
+
+// Query gets the query string of this request.
+func (req *Request) Query() string {
+ return req.q
+}
+
+// File sets a file to upload.
+// Files are only supported with a Client that was created with
+// the UseMultipartForm option.
+func (req *Request) File(fieldname, filename string, r io.Reader) {
+ req.files = append(req.files, File{
+ Field: fieldname,
+ Name: filename,
+ R: r,
+ })
+}
+
+// File represents a file to upload.
+type File struct {
+ Field string
+ Name string
+ R io.Reader
+}
+
+// hideHeaders creates a copy of headers
+// with specified fields censored (eg 'password' -> '********')
+// Additionally by default censors:
+// - Authorization
+func hideHeaders(headers http.Header, toHide ...string) http.Header {
+ toHide = append(toHide, "Authorization")
+ hs := headers.Clone()
+ for _, h := range toHide {
+ v, ok := hs[h]
+ if ok {
+ for i := range v {
+ hs[h][i] = strings.Repeat("*", utf8.RuneCountInString(v[i]))
+ }
+ }
+ }
+ return hs
+}
diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go
new file mode 100644
index 00000000..f2a9b23e
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_json_test.go
@@ -0,0 +1,233 @@
+package graphql
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/matryer/is"
+)
+
+func TestDoJSON(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ b, err := ioutil.ReadAll(r.Body)
+ is.NoErr(err)
+ is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n")
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL)
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.NoErr(err)
+ is.Equal(calls, 1) // calls
+ is.Equal(responseData["something"], "yes")
+}
+
+func TestDoJSONServerError(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ b, err := ioutil.ReadAll(r.Body)
+ is.NoErr(err)
+ is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n")
+ w.WriteHeader(http.StatusInternalServerError)
+ io.WriteString(w, `Internal Server Error`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL)
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.Equal(calls, 1) // calls
+ is.Equal(err.Error(), "graphql: server returned a non-200 status code: 500")
+}
+
+func TestDoJSONBadRequestErr(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ b, err := ioutil.ReadAll(r.Body)
+ is.NoErr(err)
+ is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n")
+ w.WriteHeader(http.StatusBadRequest)
+ io.WriteString(w, `{
+ "errors": [{
+ "message": "miscellaneous message as to why the the request was bad"
+ }]
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL)
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.Equal(calls, 1) // calls
+ is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad")
+}
+
+func TestDoJSONErrWithExtensions(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ b, err := ioutil.ReadAll(r.Body)
+ is.NoErr(err)
+ is.Equal(string(b), `{"query":"query {}","variables":null}`+"\n")
+ w.WriteHeader(http.StatusOK)
+ io.WriteString(w, `{
+ "errors": [{
+ "message": "miscellaneous message as to why the the request was bad",
+ "extensions": {
+ "code": "400"
+ }
+ }]
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL)
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.Equal(calls, 1) // calls
+ is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad")
+ is.Equal(err.(ExtendedError).Extensions()["code"], "400")
+}
+
+func TestQueryJSON(t *testing.T) {
+ is := is.New(t)
+
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ b, err := ioutil.ReadAll(r.Body)
+ is.NoErr(err)
+ is.Equal(string(b), `{"query":"query {}","variables":{"username":"matryer"}}`+"\n")
+ _, err = io.WriteString(w, `{"data":{"value":"some data"}}`)
+ is.NoErr(err)
+ }))
+ defer srv.Close()
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
+ client := NewClient(srv.URL)
+
+ req := NewRequest("query {}")
+ req.Var("username", "matryer")
+
+ // check variables
+ is.True(req != nil)
+ is.Equal(req.vars["username"], "matryer")
+
+ var resp struct {
+ Value string
+ }
+ err := client.Run(ctx, req, &resp)
+ is.NoErr(err)
+ is.Equal(calls, 1)
+
+ is.Equal(resp.Value, "some data")
+}
+
+func TestHeader(t *testing.T) {
+ is := is.New(t)
+
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Header.Get("X-Custom-Header"), "123")
+
+ _, err := io.WriteString(w, `{"data":{"value":"some data"}}`)
+ is.NoErr(err)
+ }))
+ defer srv.Close()
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
+ client := NewClient(srv.URL)
+
+ req := NewRequest("query {}")
+ req.Header.Set("X-Custom-Header", "123")
+
+ var resp struct {
+ Value string
+ }
+ err := client.Run(ctx, req, &resp)
+ is.NoErr(err)
+ is.Equal(calls, 1)
+
+ is.Equal(resp.Value, "some data")
+}
+
+func TestHideAuthInJSON(t *testing.T) {
+ is := is.New(t)
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL)
+
+ var cout bytes.Buffer
+ client.Log = func(s string) {
+ _, err := cout.WriteString(s)
+ is.NoErr(err)
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+
+ header := make(http.Header)
+ header["Authorization"] = []string{"some secret key", "another secret key"}
+ req := Request{
+ q: "query {}",
+ Header: header,
+ }
+
+ err := client.Run(ctx, &req, &responseData)
+ is.NoErr(err)
+ is.Equal(responseData["something"], "yes")
+ is.True(!strings.Contains(cout.String(), "secret key"))
+}
diff --git a/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go
new file mode 100644
index 00000000..4bf3fb8c
--- /dev/null
+++ b/tests/test/compass-runtime-agent/testkit/third_party/machinebox/graphql/graphql_multipart_test.go
@@ -0,0 +1,302 @@
+package graphql
+
+import (
+ "bytes"
+ "context"
+ "io"
+ "io/ioutil"
+ "net/http"
+ "net/http/httptest"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/matryer/is"
+)
+
+func TestWithClient(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ testClient := &http.Client{
+ Transport: roundTripperFunc(func(req *http.Request) (*http.Response, error) {
+ calls++
+ resp := &http.Response{
+ Body: ioutil.NopCloser(strings.NewReader(`{"data":{"key":"value"}}`)),
+ }
+ return resp, nil
+ }),
+ }
+
+ ctx := context.Background()
+ client := NewClient("", WithHTTPClient(testClient), UseMultipartForm())
+
+ req := NewRequest(``)
+ client.Run(ctx, req, nil)
+
+ is.Equal(calls, 1) // calls
+}
+
+func TestDoUseMultipartForm(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.NoErr(err)
+ is.Equal(calls, 1) // calls
+ is.Equal(responseData["something"], "yes")
+}
+
+func TestImmediatelyCloseReqBody(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, ImmediatelyCloseReqBody(), UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.NoErr(err)
+ is.Equal(calls, 1) // calls
+ is.Equal(responseData["something"], "yes")
+}
+
+func TestDoErr(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ io.WriteString(w, `{
+ "errors": [{
+ "message": "Something went wrong"
+ }]
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.True(err != nil)
+ is.Equal(err.Error(), "graphql: Something went wrong")
+}
+
+func TestDoServerErr(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ w.WriteHeader(http.StatusInternalServerError)
+ io.WriteString(w, `Internal Server Error`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.Equal(err.Error(), "graphql: server returned a non-200 status code: 500")
+}
+
+func TestDoBadRequestErr(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ w.WriteHeader(http.StatusBadRequest)
+ io.WriteString(w, `{
+ "errors": [{
+ "message": "miscellaneous message as to why the the request was bad"
+ }]
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &Request{q: "query {}"}, &responseData)
+ is.Equal(err.Error(), "graphql: miscellaneous message as to why the the request was bad")
+}
+
+func TestDoNoResponse(t *testing.T) {
+ is := is.New(t)
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ is.Equal(r.Method, http.MethodPost)
+ query := r.FormValue("query")
+ is.Equal(query, `query {}`)
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ err := client.Run(ctx, &Request{q: "query {}"}, nil)
+ is.NoErr(err)
+ is.Equal(calls, 1) // calls
+}
+
+func TestQuery(t *testing.T) {
+ is := is.New(t)
+
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ query := r.FormValue("query")
+ is.Equal(query, "query {}")
+ is.Equal(r.FormValue("variables"), `{"username":"matryer"}`+"\n")
+ _, err := io.WriteString(w, `{"data":{"value":"some data"}}`)
+ is.NoErr(err)
+ }))
+ defer srv.Close()
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ req := NewRequest("query {}")
+ req.Var("username", "matryer")
+
+ // check variables
+ is.True(req != nil)
+ is.Equal(req.vars["username"], "matryer")
+
+ var resp struct {
+ Value string
+ }
+ err := client.Run(ctx, req, &resp)
+ is.NoErr(err)
+ is.Equal(calls, 1)
+
+ is.Equal(resp.Value, "some data")
+}
+
+func TestFile(t *testing.T) {
+ is := is.New(t)
+
+ var calls int
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ calls++
+ file, header, err := r.FormFile("file")
+ is.NoErr(err)
+ defer file.Close()
+ is.Equal(header.Filename, "filename.txt")
+
+ b, err := ioutil.ReadAll(file)
+ is.NoErr(err)
+ is.Equal(string(b), `This is a file`)
+
+ _, err = io.WriteString(w, `{"data":{"value":"some data"}}`)
+ is.NoErr(err)
+ }))
+ defer srv.Close()
+ ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
+ defer cancel()
+ client := NewClient(srv.URL, UseMultipartForm())
+ f := strings.NewReader(`This is a file`)
+ req := NewRequest("query {}")
+ req.File("file", "filename.txt", f)
+ err := client.Run(ctx, req, nil)
+ is.NoErr(err)
+}
+
+type roundTripperFunc func(req *http.Request) (*http.Response, error)
+
+func (fn roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
+ return fn(req)
+}
+
+func TestHideAuthInMultipartForm(t *testing.T) {
+ is := is.New(t)
+ srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ io.WriteString(w, `{
+ "data": {
+ "something": "yes"
+ }
+ }`)
+ }))
+ defer srv.Close()
+
+ ctx := context.Background()
+ client := NewClient(srv.URL, UseMultipartForm())
+
+ var cout bytes.Buffer
+ client.Log = func(s string) {
+ _, err := cout.WriteString(s)
+ is.NoErr(err)
+ }
+
+ header := make(http.Header)
+ header["Authorization"] = []string{"some secret key", "another secret key"}
+ req := Request{
+ q: "query {}",
+ Header: header,
+ }
+
+ ctx, cancel := context.WithTimeout(ctx, 1*time.Second)
+ defer cancel()
+ var responseData map[string]interface{}
+ err := client.Run(ctx, &req, &responseData)
+ is.NoErr(err)
+ is.Equal(responseData["something"], "yes")
+ is.True(!strings.Contains(cout.String(), "secret key"))
+}
diff --git a/tests/tools/external-api-mock-app/config.go b/tests/tools/external-api-mock-app/config.go
new file mode 100644
index 00000000..19927178
--- /dev/null
+++ b/tests/tools/external-api-mock-app/config.go
@@ -0,0 +1,54 @@
+package main
+
+import (
+ "fmt"
+)
+
+type mTLS struct {
+ caCertPath string
+ serverCertPath string
+ serverKeyPath string
+ port int
+}
+
+type Config struct {
+ LogLevel string
+ Port int
+ BasicAuthUser string
+ BasicAuthPassword string
+ OAuthClientID string
+ OAuthClientSecret string
+ RequestHeaders map[string][]string
+ RequestQueryParameters map[string][]string
+ mTLS mTLS
+ mTLSExpiredCerts mTLS
+}
+
+func NewConfig() *Config {
+ return &Config{
+ LogLevel: "info",
+ Port: 8080,
+ BasicAuthUser: "user",
+ BasicAuthPassword: "passwd",
+ OAuthClientID: "clientID",
+ OAuthClientSecret: "clientSecret",
+ RequestHeaders: map[string][]string{"Hkey1": {"Hval1"}, "Hkey2": {"Hval21", "Hval22"}},
+ RequestQueryParameters: map[string][]string{"Qkey1": {"Qval1"}, "Qkey2": {"Qval21", "Qval22"}},
+ mTLS: mTLS{
+ port: 8090,
+ caCertPath: "/etc/secret-volume/ca.crt",
+ serverCertPath: "/etc/secret-volume/server.crt",
+ serverKeyPath: "/etc/secret-volume/server.key",
+ },
+ mTLSExpiredCerts: mTLS{
+ port: 8091,
+ caCertPath: "/etc/expired-server-cert-volume/ca.crt",
+ serverCertPath: "/etc/expired-server-cert-volume/server.crt",
+ serverKeyPath: "/etc/expired-server-cert-volume/server.key",
+ },
+ }
+}
+
+func (c *Config) String() string {
+ return fmt.Sprintf("LogLevel: %s", c.LogLevel)
+}
diff --git a/tests/tools/external-api-mock-app/server.go b/tests/tools/external-api-mock-app/server.go
new file mode 100644
index 00000000..b469380a
--- /dev/null
+++ b/tests/tools/external-api-mock-app/server.go
@@ -0,0 +1,79 @@
+package main
+
+import (
+ "crypto/tls"
+ "crypto/x509"
+ "fmt"
+ "github.com/kyma-project/kyma/tests/components/application-connector/internal/testkit/test-api"
+ log "github.com/sirupsen/logrus"
+ "io/ioutil"
+ "net/http"
+ "os"
+ "sync"
+)
+
+func main() {
+ cfg := NewConfig()
+ logLevel, err := log.ParseLevel(cfg.LogLevel)
+ if err != nil {
+ log.Warnf("Invalid log level: '%s', defaulting to 'info'", cfg.LogLevel)
+ logLevel = log.InfoLevel
+ }
+ log.SetLevel(logLevel)
+
+ log.Infof("Starting mock application")
+ log.Infof("Config: %s", cfg.String())
+
+ wg := sync.WaitGroup{}
+ wg.Add(3)
+
+ basicAuthCredentials := test_api.BasicAuthCredentials{User: cfg.BasicAuthUser, Password: cfg.BasicAuthPassword}
+ oAuthCredentials := test_api.OAuthCredentials{ClientID: cfg.OAuthClientID, ClientSecret: cfg.OAuthClientSecret}
+ expectedRequestParameters := test_api.ExpectedRequestParameters{Headers: cfg.RequestHeaders, QueryParameters: cfg.RequestQueryParameters}
+ oauthTokens := make(map[string]test_api.OAuthToken)
+ csrfTokens := make(test_api.CSRFTokens)
+
+ go func() {
+ address := fmt.Sprintf(":%d", cfg.Port)
+ router := test_api.SetupRoutes(os.Stdout, basicAuthCredentials, oAuthCredentials, expectedRequestParameters, oauthTokens, csrfTokens)
+ log.Fatal(http.ListenAndServe(address, router))
+ }()
+
+ go func() {
+ address := fmt.Sprintf(":%d", cfg.mTLS.port)
+ router := test_api.SetupMTLSRoutes(os.Stdout, oAuthCredentials, oauthTokens, csrfTokens)
+ mtlsServer := newMTLSServer(cfg.mTLS.caCertPath, address, router)
+ log.Fatal(mtlsServer.ListenAndServeTLS(cfg.mTLS.serverCertPath, cfg.mTLS.serverKeyPath))
+ }()
+
+ go func() {
+ address := fmt.Sprintf(":%d", cfg.mTLSExpiredCerts.port)
+ router := test_api.SetupMTLSRoutes(os.Stdout, oAuthCredentials, oauthTokens, csrfTokens)
+ mtlsServer := newMTLSServer(cfg.mTLSExpiredCerts.caCertPath, address, router)
+ log.Fatal(mtlsServer.ListenAndServeTLS(cfg.mTLSExpiredCerts.serverCertPath, cfg.mTLSExpiredCerts.serverKeyPath))
+ }()
+
+ wg.Wait()
+}
+
+func newMTLSServer(caCertPath, address string, handler http.Handler) *http.Server {
+ // Create a CA certificate pool and add cert.pem to it
+ caCert, err := ioutil.ReadFile(caCertPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ caCertPool := x509.NewCertPool()
+ caCertPool.AppendCertsFromPEM(caCert)
+
+ // Create the TLS Config with the CA pool and enable Client certificate validation
+ tlsConfig := &tls.Config{
+ ClientCAs: caCertPool,
+ ClientAuth: tls.RequireAndVerifyClientCert,
+ }
+
+ return &http.Server{
+ Addr: address,
+ Handler: handler,
+ TLSConfig: tlsConfig,
+ }
+}