diff --git a/test/cucumber-tests/CRs/artifacts.yaml b/test/cucumber-tests/CRs/artifacts.yaml new file mode 100644 index 000000000..b7a598115 --- /dev/null +++ b/test/cucumber-tests/CRs/artifacts.yaml @@ -0,0 +1,77 @@ +# -------------------------------------------------------------------- +# Copyright (c) 2023, WSO2 LLC. (http://wso2.com) All Rights Reserved. +# +# 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. +# ----------------------------------------------------------------------- + +apiVersion: v1 +kind: Service +metadata: + name: backend + namespace: apk-integration-test +spec: + ports: + - name: http + port: 80 + targetPort: 80 + selector: + app: httpbin +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: httpbin + namespace: apk-integration-test +spec: + replicas: 1 + selector: + matchLabels: + app: httpbin + template: + metadata: + labels: + app: httpbin + spec: + containers: + - image: docker.io/kennethreitz/httpbin:latest + imagePullPolicy: IfNotPresent + name: httpbin + ports: + - containerPort: 80 + resources: + requests: + memory: "200Mi" + cpu: "300m" + limits: + memory: "200Mi" + cpu: "300m" +--- +apiVersion: v1 +kind: Secret +metadata: + name: backend-creds + namespace: apk-integration-test +data: + username: YWRtaW4= + password: YWRtaW4= +type: Opaque +--- +apiVersion: v1 +kind: Secret +metadata: + name: backend-creds-1 + namespace: apk-integration-test +data: + username: ZHNmZHNmc2Rmc2Rm + password: YWRtaW4= +type: Opaque diff --git a/test/cucumber-tests/scripts/setup-hosts.sh b/test/cucumber-tests/scripts/setup-hosts.sh index 67ee76586..af79ad19e 100644 --- a/test/cucumber-tests/scripts/setup-hosts.sh +++ b/test/cucumber-tests/scripts/setup-hosts.sh @@ -1,5 +1,7 @@ #!/usr/bin/env bash +kubectl apply -f ./CRs/artifacts.yaml +kubectl wait deployment/httpbin -n apk-integration-test --for=condition=available --timeout=600s kubectl wait --timeout=5m -n apk-integration-test deployment/apk-test-setup-wso2-apk-adapter-deployment --for=condition=Available kubectl wait --timeout=15m -n apk-integration-test deployment/apk-test-setup-wso2-apk-gateway-runtime-deployment --for=condition=Available IP=$(kubectl get svc apk-test-setup-wso2-apk-router-service -n apk-integration-test --output jsonpath='{.status.loadBalancer.ingress[0].ip}') diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java index 2cb289a2d..52d573bbd 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java @@ -91,11 +91,7 @@ public void make_a_deployment_request() throws Exception { sharedContext.setResponse(response); } - @Then("the response body should contain {string}") - public void theResponseBodyShouldContain(String expectedText) throws IOException { - Assert.assertTrue(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse()).contains(expectedText)); - } @When("I undeploy the API whose ID is {string}") public void i_undeploy_the_api_whose_id_is(String apiID) throws Exception { diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java index c315bee50..fad18671e 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -17,16 +17,26 @@ package org.wso2.apk.integration.api; +import io.cucumber.core.options.CurlOption; +import io.cucumber.datatable.DataTable; import io.cucumber.java.Before; +import io.cucumber.java.en.And; import io.cucumber.java.en.Given; import io.cucumber.java.en.Then; +import io.cucumber.java.en.When; +import org.apache.http.Header; import org.apache.http.HttpResponse; +import org.apache.http.client.methods.CloseableHttpResponse; import org.testng.Assert; import org.wso2.apk.integration.utils.Constants; import org.wso2.apk.integration.utils.Utils; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; +import java.io.IOException; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -36,6 +46,7 @@ public class BaseSteps { private final SharedContext sharedContext; private SimpleHTTPClient httpClient; + private static final int MAX_WAIT_FOR_NEXT_MINUTE_IN_SECONDS = 10; public BaseSteps(SharedContext sharedContext) { @@ -53,6 +64,12 @@ public void systemIsReady() { } + @Then("the response body should contain {string}") + public void theResponseBodyShouldContain(String expectedText) throws IOException { + String body = SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse()); + Assert.assertTrue(body.contains(expectedText), "Actual response body: " + body); + } + @Then("the response status code should be {int}") public void theResponseStatusCodeShouldBe(int expectedStatusCode) { @@ -60,6 +77,78 @@ public void theResponseStatusCodeShouldBe(int expectedStatusCode) { Assert.assertEquals(actualStatusCode, expectedStatusCode); } + @Then("I send {string} request to {string} with body {string}") + public void sendHttpRequest(String httpMethod, String url, String body) throws IOException { + if (sharedContext.getResponse() instanceof CloseableHttpResponse) { + ((CloseableHttpResponse) sharedContext.getResponse()).close(); + } + if (CurlOption.HttpMethod.GET.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doGet(url, sharedContext.getHeaders())); + } else if (CurlOption.HttpMethod.POST.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doPost(url, sharedContext.getHeaders(), body, null)); + } else if (CurlOption.HttpMethod.PUT.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doPut(url, sharedContext.getHeaders(), body, null)); + } else if (CurlOption.HttpMethod.DELETE.toString().toLowerCase().equals(httpMethod.toLowerCase())) { + sharedContext.setResponse(httpClient.doPut(url, sharedContext.getHeaders(), body, null)); + } + } + + @Then("I set headers") + public void setHeaders(DataTable dataTable) { + List> rows = dataTable.asLists(String.class); + for (List columns : rows) { + String key = columns.get(0); + String value = columns.get(1); + key = Utils.resolveVariables(key, sharedContext.getValueStore()); + value = Utils.resolveVariables(value, sharedContext.getValueStore()); + sharedContext.addHeader(key, value); + } + } + + @Then("I eventually receive {int} response code, not accepting") + public void eventualSuccess(int statusCode, DataTable dataTable) throws IOException, InterruptedException { + List nonAcceptableCodes = dataTable.asList(Integer.class); + if (sharedContext.getResponse().getStatusLine().getStatusCode() == statusCode) { + Assert.assertTrue(true); + } else { + HttpResponse httpResponse = httpClient.executeLastRequestForEventualConsistentResponse(statusCode, + nonAcceptableCodes); + sharedContext.setResponse(httpResponse); + Assert.assertEquals(httpResponse.getStatusLine().getStatusCode(), statusCode); + } + } + + @Then("I wait for next minute") + public void waitForNextMinute() throws InterruptedException { + LocalDateTime now = LocalDateTime.now(); + LocalDateTime nextMinute = now.plusMinutes(1).withSecond(0).withNano(0); + long secondsToWait = now.until(nextMinute, ChronoUnit.SECONDS); + if (secondsToWait > MAX_WAIT_FOR_NEXT_MINUTE_IN_SECONDS) { + return; + } + Thread.sleep((secondsToWait+1) * 1000); + } + + @Then("the response headers contains key {string} and value {string} ") + public void containsHeader(String key, String value) { + key = Utils.resolveVariables(key, sharedContext.getValueStore()); + value = Utils.resolveVariables(value, sharedContext.getValueStore()); + HttpResponse response = sharedContext.getResponse(); + if (response == null) { + Assert.fail("Response is null."); + } + Header header = response.getFirstHeader(key); + if (header == null) { + Assert.fail("Could not find a header with the given key: " + key); + } + if ("*".equals(value)) { + return; // Any value is acceptable + } + String actualValue = header.getValue(); + Assert.assertEquals("Header with key found but value mismatched.", value, actualValue); + } + + @Given("I have a valid subscription") public void iHaveValidSubscription() throws Exception { @@ -70,5 +159,6 @@ public void iHaveValidSubscription() throws Exception { HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=client_credentials", Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); sharedContext.setAccessToken(Utils.extractToken(httpResponse)); + sharedContext.addStoreValue("accessToken", sharedContext.getAccessToken()); } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index 80fe052e1..309449e1f 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -23,12 +23,19 @@ import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; public class SharedContext { private SimpleHTTPClient httpClient; private String accessToken; private HttpResponse response; + private HashMap valueStore = new HashMap<>(); + private HashMap headers = new HashMap<>(); + public SimpleHTTPClient getHttpClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { if (httpClient == null) { httpClient = new SimpleHTTPClient(); @@ -55,4 +62,25 @@ public void setResponse(HttpResponse response) { this.response = response; } + + public Object getStoreValue(String key) { + return valueStore.get(key); + } + + public void addStoreValue(String key, Object value) { + valueStore.put(key, value); + } + + public Map getValueStore() { + return Collections.unmodifiableMap(valueStore); + } + + public Map getHeaders() { + return Collections.unmodifiableMap(headers); + } + + public void addHeader(String key, String value) { + headers.put(key, value); + } + } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java index 20814dde6..2097b61d0 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java @@ -29,6 +29,11 @@ import java.io.InputStreamReader; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Utils { @@ -86,4 +91,24 @@ public static String extractToken(HttpResponse response) throws IOException { } throw new IOException("Missing key [access_token] in the response from the OAuth server"); } + + public static String resolveVariables(String input, Map valueStore) { + // Define the pattern to match variables like ${variableName} + Pattern pattern = Pattern.compile("\\$\\{([^}]*)\\}"); + Matcher matcher = pattern.matcher(input); + StringBuffer resolvedString = new StringBuffer(); + + while (matcher.find()) { + String variableName = matcher.group(1); + String variableValue = valueStore.get(variableName).toString(); + + // Replace the variable with its value from the value store if it exists + // Otherwise, keep the variable placeholder as is in the string + String replacement = (variableValue != null) ? variableValue : matcher.group(); + matcher.appendReplacement(resolvedString, Matcher.quoteReplacement(replacement)); + } + + matcher.appendTail(resolvedString); + return resolvedString.toString(); + } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java index d42f2f367..c2d678c84 100644 --- a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/clients/SimpleHTTPClient.java @@ -23,6 +23,8 @@ import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpHead; @@ -43,6 +45,7 @@ import org.apache.http.impl.client.HttpClients; import org.apache.http.ssl.SSLContexts; import org.wso2.apk.integration.utils.MultipartFilePart; +import org.wso2.apk.integration.utils.exceptions.TimeoutException; import java.io.BufferedReader; import java.io.File; @@ -63,6 +66,8 @@ public class SimpleHTTPClient { protected Log log = LogFactory.getLog(getClass()); private CloseableHttpClient client; + private HttpUriRequest lastRequest; + private static final int EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS = 10; public SimpleHTTPClient() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { @@ -76,6 +81,7 @@ public SimpleHTTPClient() throws NoSuchAlgorithmException, KeyStoreException, Ke .setSSLSocketFactory(csf) .evictExpiredConnections() .build(); + this.lastRequest = null; } /** @@ -113,6 +119,7 @@ public HttpResponse doGet(String url, Map headers) throws IOExce HttpUriRequest request = new HttpGet(url); setHeaders(headers, request); + this.lastRequest = request; return client.execute(request); } @@ -146,11 +153,14 @@ public void writeTo(OutputStream outputStream) throws IOException { out.close(); } }); - ent.setContentType(contentType); + if (contentType != null) { + ent.setContentType(contentType); + } if (zip) { ent.setContentEncoding("gzip"); } entityEncReq.setEntity(ent); + this.lastRequest = request; return client.execute(request); } @@ -175,6 +185,7 @@ public HttpResponse doPostWithMultipart(String url, HttpEntity httpEntity, Map file } HttpEntity mutiPartHttpEntity = entitybuilder.build(); request.setEntity(mutiPartHttpEntity); + this.lastRequest = request; return client.execute(request); } @@ -207,6 +219,7 @@ public HttpResponse doPutWithMultipart(String url, File file, Map headers) thro HttpUriRequest request = new HttpDelete(url); setHeaders(headers, request); + this.lastRequest = lastRequest; return client.execute(request); } @@ -376,6 +390,7 @@ public void writeTo(OutputStream outputStream) throws IOException { ent.setContentEncoding("gzip"); } entityEncReq.setEntity(ent); + this.lastRequest = lastRequest; return client.execute(request); } @@ -387,4 +402,39 @@ private void setHeaders(Map headers, HttpUriRequest request) { } } } + + public HttpResponse executeLastRequestForEventualConsistentResponse(int successResponseCode, List nonAcceptableCodes) throws IOException, InterruptedException { + int counter = 1; + int responseCode = -1; + while(counter < EVENTUAL_SUCCESS_RESPONSE_TIMEOUT_IN_SECONDS) { + counter++; + Thread.sleep(1000); + HttpResponse httpResponse = getClient().execute(lastRequest); + responseCode = httpResponse.getStatusLine().getStatusCode(); + if (responseCode == successResponseCode || nonAcceptableCodes.contains(responseCode)) { + return httpResponse; + } else { + ((CloseableHttpResponse)httpResponse).close(); + } + } + throw new TimeoutException("Could not receive expected response within time. Last received code: " + responseCode); + } + + private HttpClient getClient() { + + final SSLContext sslcontext; + try { + sslcontext = SSLContexts.custom() + .loadTrustMaterial(null, new TrustAllStrategy()) + .build(); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) { + throw new RuntimeException(e); + } + final SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslcontext); + + return HttpClients.custom() + .setSSLSocketFactory(csf) + .evictExpiredConnections() + .build(); + } } diff --git a/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java new file mode 100644 index 000000000..3835962a6 --- /dev/null +++ b/test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/exceptions/TimeoutException.java @@ -0,0 +1,8 @@ +package org.wso2.apk.integration.utils.exceptions; + +public class TimeoutException extends RuntimeException { + + public TimeoutException(String s) { + super(s); + } +} diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/basic_auth_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/basic_auth_conf.yaml new file mode 100644 index 000000000..0c0aa7020 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/basic_auth_conf.yaml @@ -0,0 +1,74 @@ +--- +name: "BasicAuthAPILevel" +context: "/basic-auth" +version: "3.14" +id: "basic-auth-api-test" +type: "REST" +organization: "apk-org" +defaultVersion: true +endpointConfigurations: + production: + endpoint: "http://backend:80/anything" + endpointSecurity: + enabled: true + securityType: + secretName: "backend-creds" + userNameKey: "username" + passwordKey: "password" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/get" + verb: "GET" + authTypeEnabled: true + scopes: [] + endpointConfigurations: + production: + endpoint: "http://backend:80/anything/test" + endpointSecurity: + enabled: true + securityType: + secretName: "backend-creds-1" + userNameKey: "username" + passwordKey: "password" + - target: "/post" + verb: "POST" + authTypeEnabled: true + scopes: [ ] + endpointConfigurations: + production: + endpoint: + name: "backend" + namespace: "apk-integration-test" + port: 80 + protocol: "http" + endpointSecurity: + enabled: true + securityType: + secretName: "backend-creds" + userNameKey: "username" + passwordKey: "password" + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + endpointConfigurations: + production: + endpoint: + name: "backend" + namespace: "apk-integration-test" + port: 80 + protocol: "http" + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/default_version_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/default_version_conf.yaml new file mode 100644 index 000000000..2f479aaf9 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/default_version_conf.yaml @@ -0,0 +1,31 @@ +--- +name: "DefaultVersionApi" +context: "/test-default" +id: "default-version-api-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: true +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/employees_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/employees_conf.yaml new file mode 100644 index 000000000..394807ed1 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/employees_conf.yaml @@ -0,0 +1,31 @@ +--- +name: "EmployeeServiceAPI" +context: "/test" +id: "emp-api-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_basic_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_basic_conf.yaml new file mode 100644 index 000000000..d6ce2cf4d --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_basic_conf.yaml @@ -0,0 +1,37 @@ +--- +name: "JWTBasicAPI" +context: "/jwt-basic" +id: "jwt-basic-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" +authentication: + - authType: JWT + enabled: true + sendTokenToUpstream: true + headerName: Authorization + headerEnable: true diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_custom_header_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_custom_header_conf.yaml new file mode 100644 index 000000000..85ac20eb8 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_custom_header_conf.yaml @@ -0,0 +1,51 @@ +--- +name: "JWTCustomHeaderAPI" +context: "/jwt-custom-header" +id: "jwt-custom-header-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "http://backend:80/anything" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" +authentication: + - authType: JWT + enabled: true + sendTokenToUpstream: true + headerName: testAuth + headerEnable: true +apiPolicies: + request: + - policyName: "BackendJwt" + parameters: + enabled: true + encoding: base64 + signingAlgorithm: SHA256withRSA + header: X-JWT-Assertion + tokenTTL: 3600 + customClaims: + - claim: claim1 + value: value1 + - claim: claim2 + value: value2 diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_disabled_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_disabled_conf.yaml new file mode 100644 index 000000000..b8a9b4b0a --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/jwt_disabled_conf.yaml @@ -0,0 +1,37 @@ +--- +name: "JWTDisabledAPI" +context: "/jwt-disabled" +id: "jwt-disabled-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" +authentication: + - authType: JWT + enabled: false + sendTokenToUpstream: true + headerName: Authorization + headerEnable: true diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_conf.yaml new file mode 100644 index 000000000..d9fb7b321 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_conf.yaml @@ -0,0 +1,34 @@ +--- +name: "SimpleRateLimitAPI" +context: "/simple-rl" +id: "simple-rl-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" +apiRateLimit: + requestsPerUnit: 1 + unit: "Minute" diff --git a/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_resource_conf.yaml b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_resource_conf.yaml new file mode 100644 index 000000000..bf2a1c6ba --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/apk-confs/simple_rl_resource_conf.yaml @@ -0,0 +1,34 @@ +--- +name: "SimpleRateLimitResourceLevelAPI" +context: "/simple-rl-r" +id: "simple-rl-r-test" +version: "3.14" +type: "REST" +organization: "apk-org" +defaultVersion: false +endpointConfigurations: + production: + endpoint: "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4" +operations: + - target: "/employee" + verb: "GET" + authTypeEnabled: true + scopes: [] + - target: "/employee" + verb: "POST" + authTypeEnabled: true + scopes: [] + operationRateLimit: + requestsPerUnit: 1 + unit: "Minute" + - target: "/employee/{employeeId}" + verb: "PUT" + authTypeEnabled: true + scopes: [] + - target: "/employee/{employeeId}" + verb: "DELETE" + authTypeEnabled: true + scopes: [] +vhosts: + production: + - "default.gw.wso2.com" diff --git a/test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json b/test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json new file mode 100644 index 000000000..f45a965d9 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/definitions/basic_auth_api.json @@ -0,0 +1,225 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "EmployeeServiceAPI", + "version": "3.14" + }, + "servers": [ + { + "url": "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4", + "description": "Server URL", + "variables": {} + } + ], + "paths": { + "/employee": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "addEmployee", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + }, + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "/get": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "/post": { + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "/employee/{employeeId}": { + "put": { + "tags": [ + "employee-controller" + ], + "operationId": "editEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + }, + "delete": { + "tags": [ + "employee-controller" + ], + "operationId": "deleteEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Employee": { + "type": "object", + "properties": { + "empId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string" + }, + "salary": { + "type": "number", + "format": "double" + } + } + } + } + } +} diff --git a/test/cucumber-tests/src/test/resources/artifacts/definitions/default_version_api.json b/test/cucumber-tests/src/test/resources/artifacts/definitions/default_version_api.json new file mode 100644 index 000000000..f30f8f6a2 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/definitions/default_version_api.json @@ -0,0 +1,158 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "DefaultVersionAPI", + "version": "3.14" + }, + "servers": [ + { + "url": "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4", + "description": "Server URL", + "variables": {} + } + ], + "paths": { + "/employee": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "addEmployee", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + }, + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "/employee/{employeeId}": { + "put": { + "tags": [ + "employee-controller" + ], + "operationId": "editEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + }, + "delete": { + "tags": [ + "employee-controller" + ], + "operationId": "deleteEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Employee": { + "type": "object", + "properties": { + "empId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string" + }, + "salary": { + "type": "number", + "format": "double" + } + } + } + } + } +} diff --git a/test/cucumber-tests/src/test/resources/artifacts/definitions/employees_api.json b/test/cucumber-tests/src/test/resources/artifacts/definitions/employees_api.json new file mode 100644 index 000000000..adceefc61 --- /dev/null +++ b/test/cucumber-tests/src/test/resources/artifacts/definitions/employees_api.json @@ -0,0 +1,158 @@ +{ + "openapi": "3.0.1", + "info": { + "title": "EmployeeServiceAPI", + "version": "3.14" + }, + "servers": [ + { + "url": "https://run.mocky.io/v3/85516819-1edd-412b-a32b-a9284705a0b4", + "description": "Server URL", + "variables": {} + } + ], + "paths": { + "/employee": { + "get": { + "tags": [ + "employee-controller" + ], + "operationId": "getEmployees", + "parameters": [ + { + "name": "id", + "in": "query", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "post": { + "tags": [ + "employee-controller" + ], + "operationId": "addEmployee", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + }, + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + }, + "/employee/{employeeId}": { + "put": { + "tags": [ + "employee-controller" + ], + "operationId": "editEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + }, + "delete": { + "tags": [ + "employee-controller" + ], + "operationId": "deleteEmployee", + "parameters": [ + { + "name": "employeeId", + "in": "path", + "required": true, + "schema": { + "type": "integer", + "format": "int32" + } + } + ], + "responses": { + "200": { + "description": "default response", + "content": { + "*/*": { + "schema": { + "$ref": "#/components/schemas/Employee" + } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "Employee": { + "type": "object", + "properties": { + "empId": { + "type": "string" + }, + "name": { + "type": "string" + }, + "designation": { + "type": "string" + }, + "salary": { + "type": "number", + "format": "double" + } + } + } + } + } +} diff --git a/test/cucumber-tests/src/test/resources/tests/api/BasicAuth.feature b/test/cucumber-tests/src/test/resources/tests/api/BasicAuth.feature new file mode 100644 index 000000000..10c5e6f1e --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/BasicAuth.feature @@ -0,0 +1,33 @@ +Feature: Basic auth + Scenario: Testing API level and resource level basic auth header + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/basic_auth_conf.yaml" + And the definition file "artifacts/definitions/basic_auth_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/basic-auth/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + And the response body should contain "\"Authorization\": \"Basic YWRtaW46YWRtaW4=\"" + And I send "GET" request to "https://default.gw.wso2.com:9095/basic-auth/3.14/get" with body "" + And I eventually receive 200 response code, not accepting + |429| + And the response body should contain "\"Authorization\": \"Basic ZHNmZHNmc2Rmc2RmOmFkbWlu\"" + And I send "POST" request to "https://default.gw.wso2.com:9095/basic-auth/3.14/post" with body "" + And I eventually receive 200 response code, not accepting + |429| + And the response body should contain "\"Authorization\": \"Basic YWRtaW46YWRtaW4=\"" + + + Scenario Outline: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "" + Then the response status code should be + + Examples: + | apiID | expectedStatusCode | + | basic-auth-api-test | 202 | diff --git a/test/cucumber-tests/src/test/resources/tests/api/BasicDeploymentAndApiInvocation.feature b/test/cucumber-tests/src/test/resources/tests/api/BasicDeploymentAndApiInvocation.feature new file mode 100644 index 000000000..65c51d8ef --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/BasicDeploymentAndApiInvocation.feature @@ -0,0 +1,65 @@ +Feature: API Deployment and invocation + Scenario: Deploying an API and basic http method invocations + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/employees_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/test/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + And I send "POST" request to "https://default.gw.wso2.com:9095/test/3.14/employee/" with body "" + And the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/test/3.14/test/" with body "" + And the response status code should be 404 + And I send "PUT" request to "https://default.gw.wso2.com:9095/test/3.14/employee/12" with body "" + And the response status code should be 200 + And I send "DELETE" request to "https://default.gw.wso2.com:9095/test/3.14/employee/12" with body "" + And the response status code should be 200 + + Scenario: Deploying an API with default version + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/default_version_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/test-default/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + And I send "POST" request to "https://default.gw.wso2.com:9095/test-default/3.14/employee/" with body "" + And the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/test-default/3.14/test/" with body "" + And the response status code should be 404 + And I send "PUT" request to "https://default.gw.wso2.com:9095/test-default/3.14/employee/12" with body "" + And the response status code should be 200 + And I send "DELETE" request to "https://default.gw.wso2.com:9095/test-default/3.14/employee/12" with body "" + And the response status code should be 200 + + And I send "GET" request to "https://default.gw.wso2.com:9095/test-default/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + And I send "POST" request to "https://default.gw.wso2.com:9095/test-default/employee/" with body "" + And the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/test-default/test/" with body "" + And the response status code should be 404 + And I send "PUT" request to "https://default.gw.wso2.com:9095/test-default/employee/12" with body "" + And the response status code should be 200 + And I send "DELETE" request to "https://default.gw.wso2.com:9095/test-default/employee/12" with body "" + And the response status code should be 200 + + Scenario Outline: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "" + Then the response status code should be + + Examples: + | apiID | expectedStatusCode | + | emp-api-test | 202 | + | default-version-api-test | 202 | diff --git a/test/cucumber-tests/src/test/resources/tests/api/JWT.feature b/test/cucumber-tests/src/test/resources/tests/api/JWT.feature new file mode 100644 index 000000000..7901fb95d --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/JWT.feature @@ -0,0 +1,66 @@ +Feature: Test JWT related functionalities + Scenario: Test JWT authentication with valid and invalid access token + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/jwt_basic_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + Then I set headers + |Authorization|bearer invalidToken| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-basic/3.14/employee/" with body "" + And the response status code should be 401 + + Scenario: Test disabled JWT configuration + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/jwt_disabled_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer invalidToken| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-disabled/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + + Scenario: Test customized JWT headers + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/jwt_custom_header_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header/3.14/employee/" with body "" + And I eventually receive 401 response code, not accepting + |429| + |200| + Then I set headers + |testAuth|bearer ${accessToken}| + And I send "GET" request to "https://default.gw.wso2.com:9095/jwt-custom-header/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + And the response body should contain "\"X-Jwt-Assertion\"" + + + Scenario Outline: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "" + Then the response status code should be + + Examples: + | apiID | expectedStatusCode | + | jwt-basic-test | 202 | + | jwt-disabled-test | 202 | + | jwt-custom-header-test | 202 | diff --git a/test/cucumber-tests/src/test/resources/tests/api/SimpleRateLimit.feature b/test/cucumber-tests/src/test/resources/tests/api/SimpleRateLimit.feature new file mode 100644 index 000000000..bdc1ce78e --- /dev/null +++ b/test/cucumber-tests/src/test/resources/tests/api/SimpleRateLimit.feature @@ -0,0 +1,49 @@ +Feature: Test simple rate limit feature + Scenario: Test simple rate limit api level + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/simple_rl_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I wait for next minute + And I send "GET" request to "https://default.gw.wso2.com:9095/simple-rl/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + And I send "GET" request to "https://default.gw.wso2.com:9095/simple-rl/3.14/employee/" with body "" + Then the response status code should be 429 + + Scenario: Test simple rate limit resource level + Given The system is ready + And I have a valid subscription + When I use the APK Conf file "artifacts/apk-confs/simple_rl_resource_conf.yaml" + And the definition file "artifacts/definitions/employees_api.json" + And make the API deployment request + Then the response status code should be 200 + Then I set headers + |Authorization|bearer ${accessToken}| + And I send "POST" request to "https://default.gw.wso2.com:9095/simple-rl-r/3.14/employee/" with body "" + And I eventually receive 200 response code, not accepting + |429| + |401| + And I send "POST" request to "https://default.gw.wso2.com:9095/simple-rl-r/3.14/employee/" with body "" + Then the response status code should be 429 + And I wait for next minute + And I send "GET" request to "https://default.gw.wso2.com:9095/simple-rl-r/3.14/employee/" with body "" + Then the response status code should be 200 + And I send "GET" request to "https://default.gw.wso2.com:9095/simple-rl-r/3.14/employee/" with body "" + Then the response status code should be 200 + + Scenario Outline: Undeploy API + Given The system is ready + And I have a valid subscription + When I undeploy the API whose ID is "" + Then the response status code should be + + Examples: + | apiID | expectedStatusCode | + | simple-rl-test | 202 | + | simple-rl-r-test | 202 |