diff --git a/.github/workflows/release-tests.yml b/.github/workflows/release-tests.yml index cd5715afaa6..d470cdc9748 100644 --- a/.github/workflows/release-tests.yml +++ b/.github/workflows/release-tests.yml @@ -39,6 +39,13 @@ jobs: - id: ecr-login uses: ./.github/actions/ecr-login + - name: Setup CI Tooling + uses: shrink/actions-docker-extract@v3 + with: + image: tykio/ci-tools:latest + path: /usr/local/bin/. + destination: /usr/local/bin + - name: Run /ci/tests shell: bash env: diff --git a/.gitignore b/.gitignore index 9dc5cecaf00..7efd08ab9df 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,10 @@ tyk_linux_* main /coprocess/*.pb.go-e +ci/tests/specs/tmp +ci/tests/specs/node_modules +ci/tests/specs/package-lock.json +ci/tests/specs/gateway.collection.postman.json +ci/tests/specs/.env +ci/tests/specs/apps +ci/tests/specs/policies diff --git a/ci/tests/specs/.env.example b/ci/tests/specs/.env.example new file mode 100644 index 00000000000..e02d41e3917 --- /dev/null +++ b/ci/tests/specs/.env.example @@ -0,0 +1,2 @@ +PORTMAN_API_Key=example_gateway_secret +GATEWAY_IMAGE=internal/tyk-gateway diff --git a/ci/tests/specs/README.md b/ci/tests/specs/README.md new file mode 100644 index 00000000000..cc9efd39061 --- /dev/null +++ b/ci/tests/specs/README.md @@ -0,0 +1,55 @@ +# OpenAPI Specification Contract Testing with Portman + +To ensure that the schema in our gateway's OpenAPI specification matches our API, we have implemented contract tests using Portman. Portman leverages Postman collections to automatically generate contract tests from our OpenAPI specifications. + +## What the Contract Tests Are Testing: + +1. **Request Body Validation**: Verifying that the request body defined in our Swagger schema matches the request body expected by our gateway API. + +2. **Response Body Validation**: Ensuring that the response returned by our gateway API matches the response body defined in our Swagger schema. + +3. **Content-Type Validation**: Confirming that the content types defined in the request and response schemas in our Swagger documentation match those expected and returned by our gateway API. + +4. **Header Validation**: Validating that the headers sent to or returned by the gateway API are as described in our Swagger documentation. + +## Overview of the Included Files + +1. **config/portman-cli-options.json**: This file sets the configuration for running the Portman CLI (similar to `tyk.conf`). It also specifies the location of your `swagger.yml` file. Full configuration options can be found [here](https://github.com/apideck-libraries/portman#cli-usage). + +2. **config/portmanconfig.json**: [This file controls](https://github.com/apideck-libraries/portman#portman-settings) the content and how Portman generates the Postman tests. It specifies the types of tests to generate and any values to override in the generated tests. For example, to override headers or body content in the generated Postman tests, declare the overriding values in this file. + +3. **package.json**: Since Portman uses Node.js, this file specifies the Portman version to install and the command to run Portman. + +4. **testdata/populate_gateway_test_data.yaml**: A Venom test file that populates the gateway with data needed for the tests. + +**The following file is generated after running the tests:** + +- **gateway.collection.postman.json**: Contains the tests generated by Portman that Newman will execute. + +## How to Run It Locally + +To run the tests locally, install the following: + +1. [Venom](https://github.com/ovh/venom): Used to populate data needed for the Portman tests. + +2. [Portman](https://github.com/apideck-libraries/portman): Generates the contract tests from the Swagger file and converts them into Postman tests. + +3. [Newman](https://github.com/postmanlabs/newman): A Postman CLI tool needed to run the contract tests. + +Once these dependencies are installed, navigate to the `ci/tests/specs` directory and create a `.env` file using .env.example as template. Inside the `.env` file, add: + +```bash +PORTMAN_API_Key= +``` + +After adding the `PORTMAN_API_Key`, run the `task` command from ci/tests/specs directory to run a gateway instance and execute portman tests. + +You can then stop the gateway instance by running `task down` command from the ci/tests/specs directory. + +## How It Is Run on the CI + +The GitHub Action used to run these tests is `swagger-contract-tests.yml`. + +- In the CI environment, we launch a live gateway using the using th image created by release.yml GitHub action on every pull request. + +- After that, we run the task `task tests`, which uses Portman to generate and execute all the Swagger contract tests. diff --git a/ci/tests/specs/Taskfile.yml b/ci/tests/specs/Taskfile.yml new file mode 100644 index 00000000000..89773dbcaa4 --- /dev/null +++ b/ci/tests/specs/Taskfile.yml @@ -0,0 +1,37 @@ +version: "3" + +dotenv: ['.env'] + +tasks: + default: + desc: "Run the portman tests" + cmds: + - task: up + - task: tests + + tests: + desc: "Run the OpenAPI specification tests" + cmds: + - venom run testdata/populate_gateway_test_data.yaml --var bearerToken=$PORTMAN_API_Key --stop-on-failure && rm venom*.log + - npm install + - npm start + + build: + desc: "Build docker image" + cmds: + - docker compose build + + up: + desc: "Bring up env" + cmds: + - docker compose up -d --wait --force-recreate || { docker compose logs gw; exit 1; } + + down: + desc: "Shut down env" + cmds: + - docker compose down --remove-orphans + + logs: + desc: "Tail container logs (live)" + cmds: + - docker compose logs --tail=10 -f diff --git a/ci/tests/specs/config/certs.js b/ci/tests/specs/config/certs.js new file mode 100644 index 00000000000..cbafa4c5db2 --- /dev/null +++ b/ci/tests/specs/config/certs.js @@ -0,0 +1,32 @@ +// certificate content +const certContent = ` +-----BEGIN CERTIFICATE----- +MIIDxDCCAqygAwIBAgIgEzBjeD6zmb42Re6D1cn9z2iewlkMr7nWwZkxFYphDZ8w +DQYJKoZIhvcNAQEFBQAwcjETMBEGA1UEBhMKUGVhY2h0cmVlIDEMMAoGA1UECgwD +dHlrMQwwCgYDVQQLDAN0eWsxDzANBgNVBAMMBnR5ay5pbzEdMBsGCSqGSIb3DQEJ +ARYOc3VwcG9ydEB0eWsuaW8xDzANBgNVBAMMBnR5ay5pbzAeFw0yNDAzMjUwODQ2 +MzdaFw0zNDAzMjYwODQ2MzdaMGExEzARBgNVBAYTClBlYWNodHJlZSAxDDAKBgNV +BAoMA3R5azEMMAoGA1UECwwDdHlrMQ8wDQYDVQQDDAZ0eWsuaW8xHTAbBgkqhkiG +9w0BCQEWDnN1cHBvcnRAdHlrLmlvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAjXjBHRJvcrzO8MIY2impRNRMhWoYlkKM5KIzQiyNxXJSkgXd9CoROCMr +rQdRPusmFKONvorLRw1JEVhfMiEBG+Vmpmi07zK/STBJXdPNPCBjvaHTbDIx6git +ezM+aKIDdnYoMDIx9T7zOukzW7AxzZ566oJETNgsz4lcleAwempqRndT91Nf5A97 +QAEL+LnDBpInE2rr36pAKxDRsMAaaBFpgM+tF2+6jd0Sh2gQCA904ZqeOOum7IqU +KGOfYEQVjicTr+TF6V5N5EVw71jwOZt9WPO7lOh7wRmdgNp0MRFVUp1rITiIYstv +AwdWEc7nbIj/VhMbAvX1za5hzlSNpwIDAQABo1cwVTAdBgNVHQ4EFgQUpNkYJfJr +H5b97SmpTzdPjTGDYbMwHwYDVR0jBBgwFoAUpNkYJfJrH5b97SmpTzdPjTGDYbMw +EwYDVR0RBAwwCoIILip0eWsuaW8wDQYJKoZIhvcNAQEFBQADggEBAIocI/ZqtXyS +j/oI+Vlh8v/4RPzFHHCCBo8JWrSavYnWdkLl3nFF1pFaXvemlTGgUGL3c8GlujO9 +qiCKtza9XRdPPlZhbUOSTRZ0ck/KEQBZDar6juf2Ye2OgJhzeSsLyXQHD8Co6nGf +4HE41MmhKSAUm+QIZMFRS98EkARkmtqhqehYhTvaKpvzEaRbMW+y5Dh1f6y0bYos +Vx4NZLOF1C3/eEZywHJaPAtkPxjlhRVmgjJLDqEaj+DzwFYK2RMbRHCh8LKK8GIS +CWeLM/p2ROlKFup25txezId1cmrhSP4m4djAX6IkgZSy1tDv3UmQQpPIrc2zdsmb +4KKpTyHqfYo= +-----END CERTIFICATE----- +`; + +// Set the request body to the certificate content +pm.request.body.update({ + mode: 'raw', + raw: certContent +}); diff --git a/ci/tests/specs/config/portman-cli-options.json b/ci/tests/specs/config/portman-cli-options.json new file mode 100644 index 00000000000..14dbd1108ad --- /dev/null +++ b/ci/tests/specs/config/portman-cli-options.json @@ -0,0 +1,12 @@ +{ + "local": "../../../swagger.yml", + "baseUrl": "http://localhost:8080", + "output": "gateway.collection.postman.json", + "oaOutput": "filtered.gateway.swagger.yml", + "includeTests": true, + "syncPostman": false, + "runNewman": true, + "envFile": ".env", + "ignoreCircularRefs": true, + "portmanConfigFile":"config/portmanconfig.json" +} diff --git a/ci/tests/specs/config/portmanconfig.json b/ci/tests/specs/config/portmanconfig.json new file mode 100644 index 00000000000..8e578c35451 --- /dev/null +++ b/ci/tests/specs/config/portmanconfig.json @@ -0,0 +1,361 @@ +{ + "version": 1.0, + "$schema": "https://raw.githubusercontent.com/apideck-libraries/portman/main/src/utils/portman-config-schema.json", + "tests": { + "contractTests": [ + { + "openApiOperation": "*::/*", + "excludeForOperations": [ + "batch" + ], + "statusSuccess": { + "enabled": true + } + }, + { + "openApiOperation": "*::/*", + "excludeForOperations": [ + "batch" + ], + "responseTime": { + "enabled": false, + "maxMs": 300 + } + }, + { + "openApiOperation": "*::/*", + "excludeForOperations": [ + "batch" + ], + "contentType": { + "enabled": true + } + }, + { + "openApiOperation": "*::/*", + "excludeForOperations": [ + "batch" + ], + "jsonBody": { + "enabled": true + } + }, + { + "openApiOperation": "*::/*", + "excludeForOperations": [ + "validateAKeyDefinition", + "listCerts", + "batch" + ], + "schemaValidation": { + "enabled": true + } + }, + { + "openApiOperation": "*::/*", + "headersPresent": { + "enabled": true + } + } + ], + "contentTests": [], + "extendTests": [ + ], + "variationTests": [] + }, + "assignVariables": [ + { + "openApiOperationIds": [ + "addPolicy" + ], + "collectionVariables": [ + { + "responseBodyProp": "key", + "name": "addPolicy.ID" + } + ] + }, + { + "openApiOperationIds": [ + "addCert" + ], + "collectionVariables": [ + { + "responseBodyProp": "id", + "name": "addCert.id" + } + ] + }, + { + "openApiOperationIds": [ + "addKey" + ], + "collectionVariables": [ + { + "responseBodyProp": "key_hash", + "name": "addKey.key_hash" + }, + { + "responseBodyProp": "key", + "name": "addKey.key" + } + ] + }, + { + "openApiOperationIds": [ + "createApiOAS" + ], + "collectionVariables": [ + { + "responseBodyProp": "key", + "name": "createApiOAS.apiID" + } + ] + }, + { + "openApiOperationIds": [ + "createApi" + ], + "collectionVariables": [ + { + "responseBodyProp": "key", + "name": "createApi.apiID" + } + ] + } + ], + "operationPreRequestScripts": [ + { + "openApiOperationIds": [ + "addCert" + ], + "scripts": [ + "file:config/certs.js" + ] + }, + { + "openApiOperationIds": [ + "updateApi", + "deleteApi", + "getApi", + "updateApi", + "listApiVersions", + "deleteOASApi", + "getOASApi", + "patchApiOAS", + "updateApiOAS", + "downloadApiOASPublic", + "getPolicy" + ], + "scripts": [ + "file:config/prescript.js" + ] + } + ], + "overwrites": [ + { + "openApiOperationIds": [ + "getPolicy", + "deletePolicy", + "updatePolicy" + ], + "overwriteRequestPathVariables": [ + { + "value": "{{addPolicy.ID}}", + "key": "polID", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "listOASApiVersions", + "deleteOASApi", + "getOASApi", + "patchApiOAS", + "updateApiOAS", + "downloadApiOASPublic" + ], + "overwriteRequestPathVariables": [ + { + "value": "{{createApiOAS.apiID}}", + "overwrite": true, + "key": "apiID" + } + ] + }, + { + "openApiOperationIds": [ + "batch" + ], + "overwriteRequestPathVariables": [ + { + "key": "listen_path", + "value": "tyk-keyless-api", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "deleteCerts", + "listCertsWithIDs" + ], + "overwriteRequestPathVariables": [ + { + "key": "certID", + "value": "{{addCert.id}}", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "getKey", + "updateKey", + "deleteKey" + ], + "overwriteRequestPathVariables": [ + { + "key": "keyID", + "value": "{{addKey.key}}", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "invalidateOAuthRefresh" + ], + "overwriteRequestQueryParams": [ + { + "key": "api_id", + "value": "f84ve1a04e5648c2797170567971565n" + } + ] + }, + { + "openApiOperationIds": [ + "getApisForOauthApp" + ], + "overwriteRequestQueryParams": [ + { + "key": "orgID", + "value": "" + } + ] + }, + { + "openApiOperationIds": [ + "listOAuthClients", + "getOAuthClient", + "getOAuthClientTokens", + "updateOAuthClient", + "rotateOauthClient", + "deleteOAuthClient" + ], + "overwriteRequestPathVariables": [ + { + "key": "apiID", + "value": "f84ve1a04e5648c2797170567971565n", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "createOAuthClient" + ], + "overwriteRequestBody": [ + { + "key": "api_id", + "value": "f84ve1a04e5648c2797170567971565n", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "addKey", + "createCustomKey", + "createKey", + "updateKey" + ], + "overwriteRequestBody": [ + { + "key": "apply_policies", + "overwrite": true, + "value": [ + "46ad120575961080181867e" + ] + } + ] + }, + { + "openApiOperationIds": [ + "updateApiOAS", + "patchApiOAS" + ], + "overwriteRequestBody": [ + { + "overwrite": true, + "key": "x-tyk-api-gateway.info.id", + "value": "{{createApiOAS.apiID}}" + } + ] + }, + { + "openApiOperationIds": [ + "setPoliciesToHashedKey" + ], + "overwriteRequestPathVariables": [ + { + "key": "keyID", + "value": "{{addKey.key_hash}}", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "deleteApi", + "getApi", + "updateApi", + "listApiVersions" + ], + "overwriteRequestPathVariables": [ + { + "key": "apiID", + "value": "{{createApi.apiID}}", + "overwrite": true + } + ] + }, + { + "openApiOperationIds": [ + "updateApi" + ], + "overwriteRequestBody": [ + { + "key": "api_id", + "value": "{{createApi.apiID}}", + "overwrite": true + } + ] + } + ], + "globals": { + "collectionPreRequestScripts": [ + ], + "keyValueReplacements": {}, + "valueReplacements": {}, + "rawReplacements": [], + "orderOfOperations": [ + "POST::/*", + "GET::/*", + "PUT::/*", + "PATCH::/*", + "DELETE::/*" + ] + } +} diff --git a/ci/tests/specs/config/prescript.js b/ci/tests/specs/config/prescript.js new file mode 100644 index 00000000000..e08b8c56319 --- /dev/null +++ b/ci/tests/specs/config/prescript.js @@ -0,0 +1,27 @@ +// Retrieve the API key from the environment variable +const apiKey = pm.environment.get('apiKey'); + +// Check if the API key is available +if (!apiKey) { + console.error('API_Key environment variable is not set.'); + // Optionally, you can abort the request if the API key is missing + postman.setNextRequest(null); +} else { + // Define the Tyk Gateway reload endpoint using the baseUrl + const tykGatewayReloadUrl = `${pm.variables.get('baseUrl')}/tyk/reload?block=true`; + + // Send a GET request to the reload endpoint with the API key in the header + pm.sendRequest({ + url: tykGatewayReloadUrl, + method: 'GET', + header: { + 'x-tyk-authorization': apiKey + } + }, function (err, res) { + if (err) { + console.error('Error reloading Tyk Gateway:', err); + } else { + console.log('Tyk Gateway reload response:', res.status); + } + }); +} diff --git a/ci/tests/specs/config/tyk.standalone.conf b/ci/tests/specs/config/tyk.standalone.conf new file mode 100644 index 00000000000..f0893777322 --- /dev/null +++ b/ci/tests/specs/config/tyk.standalone.conf @@ -0,0 +1,29 @@ +{ + "log_level": "info" , + "listen_port": 8080, + "secret": "example_gateway_secret", + "template_path": "/opt/tyk-gateway/templates", + "app_path": "/opt/tyk-gateway/apps/", + "storage": { + "type": "redis", + "host": "redis", + "port": 6379, + "username": "", + "password": "", + "database": 0 + }, + "enable_analytics": false, + "health_check": { + "enable_health_checks": false, + "health_check_value_timeouts": 60 + }, + "policies": { + "policy_source": "file", + "policy_path": "/opt/tyk-gateway/policies" + }, + "hash_keys": true, + "enable_hashed_keys_listing": true, + "close_connections": false, + "allow_insecure_configs": true, + "enable_batch_request_support": true +} diff --git a/ci/tests/specs/docker-compose.yml b/ci/tests/specs/docker-compose.yml new file mode 100644 index 00000000000..482bd3334b2 --- /dev/null +++ b/ci/tests/specs/docker-compose.yml @@ -0,0 +1,26 @@ +name: open-api-specification-contract-tests + +include: + - ../../../docker/services/redis.yml + - ../../../docker/services/httpbin.yml + +services: + tyk: + image: ${GATEWAY_IMAGE} + networks: [ proxy ] + build: ../../../. + depends_on: + - redis + volumes: + - ./policies:/opt/tyk-gateway/policies + - ./apps:/opt/tyk-gateway/apps + - ./config/tyk.standalone.conf:/opt/tyk-gateway/tyk.conf + ports: + - "8080:8080" + environment: + - TYK_LOGLEVEL=debug + +networks: + proxy: + name: proxy + driver: bridge diff --git a/ci/tests/specs/package.json b/ci/tests/specs/package.json new file mode 100644 index 00000000000..54615adbf67 --- /dev/null +++ b/ci/tests/specs/package.json @@ -0,0 +1,16 @@ +{ + "name": "cli-filtering", + "version": "1.0.0", + "description": "ns", + "directories": { + "example": "examples" + }, + "scripts": { + "start": "portman --cliOptionsFile=config/portman-cli-options.json" + }, + "author": "", + "license": "ISC", + "dependencies": { + "@apideck/portman": "1.30.7" + } +} diff --git a/ci/tests/specs/test.sh b/ci/tests/specs/test.sh new file mode 100755 index 00000000000..1269cb7c23d --- /dev/null +++ b/ci/tests/specs/test.sh @@ -0,0 +1,21 @@ +#!/bin/bash +set -eo pipefail + +function setup { + local tag=${1:-"v0.0.0"} + # Setup required env vars for docker compose + export GATEWAY_IMAGE=${GATEWAY_IMAGE:-"tykio/tyk-gateway:${tag}"} + + docker pull -q $GATEWAY_IMAGE +} + +setup $1 + +trap "task down" EXIT + +echo "Creating .env file..." +echo "PORTMAN_API_Key=example_gateway_secret" > ".env" + +task up + +task tests diff --git a/ci/tests/specs/testdata/populate_gateway_test_data.yaml b/ci/tests/specs/testdata/populate_gateway_test_data.yaml new file mode 100644 index 00000000000..b37f6e862a4 --- /dev/null +++ b/ci/tests/specs/testdata/populate_gateway_test_data.yaml @@ -0,0 +1,174 @@ +name: Manage APIs and Policies +testcases: + - name: Delete API + steps: + - type: http + method: DELETE + url: http://localhost:8080/tyk/apis/f84ve1a04e5648c2797170567971565n + headers: + X-Tyk-Authorization: {{ .bearerToken }} + assertions: + - result.statuscode ShouldBeIn 200 404 + + - name: Delete Keyless API + steps: + - type: http + method: DELETE + url: http://localhost:8080/tyk/apis/Nwupjt6uqbohnSxgdrTfm7p7b50a5kgU + headers: + X-Tyk-Authorization: {{ .bearerToken }} + assertions: + - result.statuscode ShouldBeIn 200 404 + + - name: Delete Policy + steps: + - type: http + method: DELETE + url: http://localhost:8080/tyk/policies/46ad120575961080181867e + headers: + X-Tyk-Authorization: {{ .bearerToken }} + assertions: + - result.statuscode ShouldBeIn 200 500 + + - name: Create API + steps: + - type: http + method: POST + url: http://localhost:8080/tyk/apis + headers: + Content-Type: application/json + X-Tyk-Authorization: {{ .bearerToken }} + body: | + { + "api_id": "f84ve1a04e5648c2797170567971565n", + "auth": { + "auth_header_name": "authorization" + }, + "definition": { + "key": "version", + "location": "header" + }, + "name": "Tyk Test API", + "org_id": "664a14650619d40001f1f00f", + "proxy": { + "listen_path": "/tyk-api-test/", + "strip_listen_path": true, + "target_url": "https://httpbin.org" + }, + "use_oauth2": true, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default" + } + } + } + } + assertions: + - result.statuscode ShouldEqual 200 + + - name: Create Keyless API + steps: + - type: http + method: POST + url: http://localhost:8080/tyk/apis + headers: + Content-Type: application/json + X-Tyk-Authorization: {{ .bearerToken }} + body: | + { + "api_id": "Nwupjt6uqbohnSxgdrTfm7p7b50a5kgU", + "use_keyless": true, + "definition": { + "key": "version", + "location": "header" + }, + "name": "Tyk Keyless API", + "org_id": "664a14650619d40001f1f00f", + "proxy": { + "listen_path": "/tyk-keyless-api/", + "strip_listen_path": true, + "target_url": "https://httpbin.org" + }, + "use_oauth2": false, + "version_data": { + "not_versioned": true, + "versions": { + "Default": { + "name": "Default" + } + } + } + } + assertions: + - result.statuscode ShouldEqual 200 + + - name: Create Policy + steps: + - type: http + method: POST + url: http://localhost:8080/tyk/policies + headers: + Content-Type: application/json + X-Tyk-Authorization: {{ .bearerToken }} + body: | + { + "access_rights": { + "itachi-api": { + "allowed_urls": [ + { + "methods": [ + "GET" + ], + "url": "/users" + } + ], + "api_id": "f84ve1a04e5648c2797170567971565n", + "api_name": "Itachi api", + "disable_introspection": false, + "versions": [ + "Default" + ] + } + }, + "active": true, + "hmac_enabled": false, + "id": "46ad120575961080181867e", + "is_inactive": false, + "key_expires_in": 2592000, + "max_query_depth": -1, + "meta_data": { + "update": "sample policy test", + "user_type": "mobile_user" + }, + "name": "Sample policy", + "partitions": { + "acl": true, + "complexity": false, + "per_api": false, + "quota": true, + "rate_limit": true + }, + "per": 60, + "quota_max": 10000, + "quota_renewal_rate": 3600, + "rate": 1000, + "tags": [ + "security" + ], + "throttle_interval": 10, + "throttle_retry_limit": 10 + } + assertions: + - result.statuscode ShouldEqual 200 + + - name: Reload gateway + steps: + - type: http + method: GET + url: http://localhost:8080/tyk/reload + headers: + X-Tyk-Authorization: {{ .bearerToken }} + assertions: + - result.statuscode ShouldBeIn 200 diff --git a/swagger.yml b/swagger.yml index 3b05e26306d..ecb7719ecbc 100644 --- a/swagger.yml +++ b/swagger.yml @@ -1787,7 +1787,7 @@ paths: summary: Return one certificate or list multiple certificates in the Tyk Gateway given a comma separated list of cert IDs. tags: - - CertsTag + - Certs /tyk/debug: post: description: Used to test API definition by sending sample request and analysing @@ -6319,6 +6319,7 @@ components: - executionEngine - subgraph - supergraph + - "" # Allow empty string type: string introspection: $ref: '#/components/schemas/GraphQLIntrospectionConfig' @@ -6343,9 +6344,9 @@ components: type: array version: enum: - - '''' - "1" - "2" + - "" # Allow empty string type: string type: object GraphQLEngineConfig: @@ -7307,6 +7308,7 @@ components: trigger: type: number type: object + nullable: true RateLimitType2: properties: per: