From 975e44444964469d7fe404f8652e519238959578 Mon Sep 17 00:00:00 2001 From: O-sura Date: Mon, 18 Mar 2024 13:29:05 +0530 Subject: [PATCH] Adding cucumber tests for GraphQL API creation --- .../cucumber-tests/CRs/artifacts.yaml | 258 ++++++++++++ .../integration/api/APIDeploymentSteps.java | 229 ++++++++--- .../wso2/apk/integration/api/BaseSteps.java | 14 +- .../apk/integration/api/SharedContext.java | 9 + .../wso2/apk/integration/utils/Constants.java | 1 + .../org/wso2/apk/integration/utils/Utils.java | 66 +++- .../definitions/schema_graphql.graphql | 199 ++++++++++ .../artifacts/payloads/gqlPayload.json | 194 +++++++++ .../artifacts/payloads/gql_with_scopes.json | 370 ++++++++++++++++++ .../resources/tests/api/Deployment.feature | 75 +++- .../resources/tests/api/ScopesEnabled.feature | 105 +++++ 11 files changed, 1458 insertions(+), 62 deletions(-) create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/schema_graphql.graphql create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gqlPayload.json create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gql_with_scopes.json create mode 100644 test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/ScopesEnabled.feature diff --git a/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml b/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml index 139597f9c..7eeb1b40f 100644 --- a/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml +++ b/test/apim-apk-agent-test/cucumber-tests/CRs/artifacts.yaml @@ -1,2 +1,260 @@ +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: graphql-faker-schema + namespace: apk +data: + schema.graphql: | + schema { + query: Query + mutation: Mutation + subscription: Subscription + } + # The query type, represents all of the entry points into our object graph + type Query { + hero(episode: Episode): Character + reviews(episode: Episode!): [Review] + search(text: String): [SearchResult] + character(id: ID!): Character + droid(id: ID!): Droid + human(id: ID!): Human + allHumans(first: Int): [Human] + allDroids(first: Int): [Droid] + allCharacters(first: Int): [Character] + starship(id: ID!): Starship + } + # The mutation type, represents all updates we can make to our data + type Mutation { + createReview(episode: Episode, review: ReviewInput!): Review + } + + # The subscription type, represents all subscriptions we can make to our data + type Subscription { + reviewAdded(episode: Episode): Review + } + + # The episodes in the Star Wars trilogy + enum Episode { + # Star Wars Episode IV: A New Hope, released in 1977. + NEWHOPE + + # Star Wars Episode V: The Empire Strikes Back, released in 1980. + EMPIRE + + # Star Wars Episode VI: Return of the Jedi, released in 1983. + JEDI + + # Star Wars Episode III: Revenge of the Sith, released in 2005 + SITH + } + + # A character from the Star Wars universe + interface Character { + # The ID of the character + id: ID! + + # The name of the character + name: String! + + # The friends of the character, or an empty list if they have none + friends: [Character] + + # The friends of the character exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this character appears in + appearsIn: [Episode]! + } + + # Units of height + enum LengthUnit { + # The standard unit around the world + METER + + # Primarily used in the United States + FOOT + } + + # A humanoid creature from the Star Wars universe + type Human implements Character { + # The ID of the human + id: ID! + + # What this human calls themselves + name: String! + + # The home planet of the human, or null if unknown + homePlanet: String + + # Height in the preferred unit, default is meters + height(unit: LengthUnit = METER): Float + + # Mass in kilograms, or null if unknown + mass: Float + + # This human's friends, or an empty list if they have none + friends: [Character] + + # The friends of the human exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this human appears in + appearsIn: [Episode]! + + # A list of starships this person has piloted, or an empty list if none + starships: [Starship] + } + + # An autonomous mechanical character in the Star Wars universe + type Droid implements Character { + # The ID of the droid + id: ID! + + # What others call this droid + name: String! + + # This droid's friends, or an empty list if they have none + friends: [Character] + + # The friends of the droid exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this droid appears in + appearsIn: [Episode]! + + # This droid's primary function + primaryFunction: String + } + + # A connection object for a character's friends + type FriendsConnection { + # The total number of friends + totalCount: Int + + # The edges for each of the character's friends. + edges: [FriendsEdge] + + # A list of the friends, as a convenience when edges are not needed. + friends: [Character] + + # Information for paginating this connection + pageInfo: PageInfo! + } + + # An edge object for a character's friends + type FriendsEdge { + # A cursor used for pagination + cursor: ID! + + # The character represented by this friendship edge + node: Character + } + + # Information for paginating this connection + type PageInfo { + startCursor: ID + endCursor: ID + hasNextPage: Boolean! + } + + # Represents a review for a movie + type Review { + # The movie + episode: Episode + + # The number of stars this review gave, 1-5 + stars: Int! + + # Comment about the movie + commentary: String + } + + # The input object sent when someone is creating a new review + input ReviewInput { + # 0-5 stars + stars: Int! + + # Comment about the movie, optional + commentary: String + + # Favorite color, optional + favorite_color: ColorInput + } + + # The input object sent when passing in a color + input ColorInput { + red: Int! + green: Int! + blue: Int! + } + + type Starship { + # The ID of the starship + id: ID! + + # The name of the starship + name: String! + + # Length of the starship, along the longest axis + length(unit: LengthUnit = METER): Float + + coordinates: [[Float!]!] + } + + union SearchResult = Human | Droid | Starship + +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: graphql-faker + namespace: apk + labels: + app: graphql-faker +spec: + replicas: 1 + selector: + matchLabels: + app: graphql-faker + template: + metadata: + labels: + app: graphql-faker + spec: + containers: + - name: graphql-faker + image: apisguru/graphql-faker + args: ["--open=false", "/etc/graphql-faker/schema.graphql"] + ports: + - containerPort: 9002 + volumeMounts: + - name: schema-volume + mountPath: /etc/graphql-faker + resources: + requests: + memory: "256Mi" + cpu: "250m" + limits: + memory: "512Mi" + cpu: "500m" + volumes: + - name: schema-volume + configMap: + name: graphql-faker-schema +--- +apiVersion: v1 +kind: Service +metadata: + name: graphql-faker-service + namespace: apk +spec: + type: LoadBalancer + ports: + - port: 9002 + targetPort: 9002 + protocol: TCP + selector: + app: graphql-faker \ No newline at end of file diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java index 9c9dc8644..90ac74e24 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/APIDeploymentSteps.java @@ -20,6 +20,7 @@ import com.google.common.io.Resources; import io.cucumber.java.en.Then; import io.cucumber.java.en.When; +import io.cucumber.java.en.Given; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; @@ -152,24 +153,25 @@ public void make_a_change_lifecycle_request() throws Exception { Thread.sleep(3000); } - @When("make the Application Creation request") - public void make_application_creation_request() throws Exception { + @When("make the Application Creation request with the name {string}") + public void make_application_creation_request(String applicationName) throws Exception { logger.info("Creating an application"); - String payload = "{\"name\":\"PetstoreApp\",\"throttlingPolicy\":\"10PerMin\",\"description\":\"test app\",\"tokenType\":\"JWT\",\"groups\":null,\"attributes\":{}}"; - + String payload = "{\"name\":\"" + applicationName + "\",\"throttlingPolicy\":\"10PerMin\",\"description\":\"test app\",\"tokenType\":\"JWT\",\"groups\":null,\"attributes\":{}}"; + Map headers = new HashMap<>(); headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getDevportalAccessToken()); headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); - + HttpResponse response = sharedContext.getHttpClient().doPost(Utils.getApplicationCreateURL(), headers, payload, Constants.CONTENT_TYPES.APPLICATION_JSON); - + sharedContext.setResponse(response); sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); logger.info("Response: " + sharedContext.getResponseBody()); sharedContext.setApplicationUUID(Utils.extractApplicationID(sharedContext.getResponseBody())); Thread.sleep(3000); } + @When("I have a KeyManager") public void i_have_a_key_manager() throws Exception { @@ -221,7 +223,7 @@ public void make_subscription_request() throws Exception { String apiUUID = sharedContext.getApiUUID(); logger.info("API UUID: " + apiUUID); logger.info("Application UUID: " + applicationUUID); - String payload = "{\"apiId\":\"" + apiUUID + "\",\"applicationId\":\"" + applicationUUID + "\",\"throttlingPolicy\":\"Gold\"}"; + String payload = "{\"apiId\":\"" + apiUUID + "\",\"applicationId\":\"" + applicationUUID + "\",\"throttlingPolicy\":\"Unlimited\"}"; Map headers = new HashMap<>(); headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getDevportalAccessToken()); @@ -261,7 +263,7 @@ public void make_access_token_generation_request() throws Exception { logger.info("Oauth Key UUID: " + oauthKeyUUID); logger.info("Application UUID: " + applicationUUID); String payload = "{\"consumerSecret\":\"" + consumerSecret + "\",\"validityPeriod\":3600,\"revokeToken\":null," + - "\"scopes\":[\"write:pets\",\"read:pets\"],\"additionalProperties\":{\"id_token_expiry_time\":3600," + + "\"scopes\":[\"write:pets\",\"read:pets\",\"query:hero\"],\"additionalProperties\":{\"id_token_expiry_time\":3600," + "\"application_access_token_expiry_time\":3600,\"user_access_token_expiry_time\":3600,\"bypassClientCredentials\":false," + "\"pkceMandatory\":false,\"pkceSupportPlain\":false,\"refresh_token_expiry_time\":86400}}"; @@ -279,6 +281,33 @@ public void make_access_token_generation_request() throws Exception { Thread.sleep(3000); } + @When("I make Access Token Generation request without scopes") + public void make_access_token_generation_request_without_scopes() throws Exception { + String applicationUUID = sharedContext.getApplicationUUID(); + String oauthKeyUUID = sharedContext.getOauthKeyUUID(); + String consumerKey = sharedContext.getConsumerKey(); + String consumerSecret = sharedContext.getConsumerSecret(); + logger.info("Oauth Key UUID: " + oauthKeyUUID); + logger.info("Application UUID: " + applicationUUID); + String payload = "{\"consumerSecret\":\"" + consumerSecret + "\",\"validityPeriod\":3600,\"revokeToken\":null," + + "\"scopes\":[],\"additionalProperties\":{\"id_token_expiry_time\":3600," + + "\"application_access_token_expiry_time\":3600,\"user_access_token_expiry_time\":3600,\"bypassClientCredentials\":false," + + "\"pkceMandatory\":false,\"pkceSupportPlain\":false,\"refresh_token_expiry_time\":86400}}"; + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getDevportalAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doPost(Utils.getAccessTokenGenerationURL(applicationUUID, oauthKeyUUID), + headers, payload, Constants.CONTENT_TYPES.APPLICATION_JSON); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setApiAccessToken(Utils.extractKeys(sharedContext.getResponseBody(), "accessToken")); + logger.info("Access Token without scopes: " + sharedContext.getApiAccessToken()); + Thread.sleep(3000); + } + @When("make the API Deployment request") public void make_a_api_deployment_request() throws Exception { @@ -328,44 +357,148 @@ public void makeAPIDeploymentFromOrganization(String organization) throws Except - @When("I undeploy the API whose ID is {string}") - public void i_undeploy_the_api_whose_id_is(String apiID) throws Exception { - - // Create query parameters - List queryParams = new ArrayList<>(); - queryParams.add(new BasicNameValuePair("apiId", apiID)); - - URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); - - Map headers = new HashMap<>(); - headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); - headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); - - HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", - Constants.CONTENT_TYPES.APPLICATION_JSON); - - sharedContext.setResponse(response); - sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); - } - - @When("I undeploy the API whose ID is {string} and organization {string}") - public void undeployAPIByIdAndOrganization(String apiID,String organization) throws Exception { - - // Create query parameters - List queryParams = new ArrayList<>(); - queryParams.add(new BasicNameValuePair("apiId", apiID)); - - URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); - - Map headers = new HashMap<>(); - Object header = sharedContext.getStoreValue(organization); - headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + header); - headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); - - HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", - Constants.CONTENT_TYPES.APPLICATION_JSON); - - sharedContext.setResponse(response); - sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); - } + // @When("I undeploy the API whose ID is {string}") + // public void i_undeploy_the_api_whose_id_is(String apiID) throws Exception { + + // // Create query parameters + // List queryParams = new ArrayList<>(); + // queryParams.add(new BasicNameValuePair("apiId", apiID)); + + // URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); + + // Map headers = new HashMap<>(); + // headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + // headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + // HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", + // Constants.CONTENT_TYPES.APPLICATION_JSON); + + // sharedContext.setResponse(response); + // sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + // } + + // @When("I undeploy the API whose ID is {string} and organization {string}") + // public void undeployAPIByIdAndOrganization(String apiID,String organization) throws Exception { + + // // Create query parameters + // List queryParams = new ArrayList<>(); + // queryParams.add(new BasicNameValuePair("apiId", apiID)); + + // URI uri = new URIBuilder(Utils.getAPIUnDeployerURL()).addParameters(queryParams).build(); + + // Map headers = new HashMap<>(); + // Object header = sharedContext.getStoreValue(organization); + // headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + header); + // headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + // HttpResponse response = sharedContext.getHttpClient().doPost(uri.toString(), headers, "", + // Constants.CONTENT_TYPES.APPLICATION_JSON); + + // sharedContext.setResponse(response); + // sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + // } + + //New steps + @Given("a valid graphql definition file") + public void iHaveValidGraphQLDefinition() throws Exception { + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addPart("file", new FileBody(definitionFile)); + + logger.info("Definition File: "+ new FileBody(definitionFile)); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_APIM_HOST); + + HttpEntity multipartEntity = builder.build(); + + HttpResponse response = sharedContext.getHttpClient().doPostWithMultipart(Utils.getGQLSchemaValidatorURL(), + multipartEntity, headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setAPIDefinitionValidStatus(Utils.extractValidStatus(sharedContext.getResponseBody())); + Thread.sleep(3000); + } + + @Then("I make the import GraphQLAPI Creation request") + public void make_import_gqlapi_creation_request() throws Exception { + + // Create a MultipartEntityBuilder to build the request entity + MultipartEntityBuilder builder = MultipartEntityBuilder.create() + .setMode(HttpMultipartMode.BROWSER_COMPATIBLE) + .addPart("additionalProperties", new FileBody(payloadFile)) + .addPart("file", new FileBody(definitionFile)); + + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpEntity multipartEntity = builder.build(); + + HttpResponse response = sharedContext.getHttpClient().doPostWithMultipart(Utils.getGQLImportAPIURL(), + multipartEntity, headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setApiUUID(Utils.extractID(sharedContext.getResponseBody())); + Thread.sleep(3000); + } + + @Then("I delete the application {string} from devportal") + public void make_application_deletion_request(String applicationName) throws Exception { + logger.info("Fetching the applications"); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getDevportalAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + List queryParams = new ArrayList<>(); + queryParams.add(new BasicNameValuePair("query", applicationName)); + + URI uri = new URIBuilder(Utils.getApplicationCreateURL()).addParameters(queryParams).build(); + HttpResponse appSearchResponse = sharedContext.getHttpClient().doGet(uri.toString(), headers); + + sharedContext.setResponse(appSearchResponse); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setApplicationUUID(Utils.extractApplicationUUID(sharedContext.getResponseBody())); + HttpResponse deleteResponse = sharedContext.getHttpClient().doDelete(Utils.getApplicationCreateURL() + "/" + sharedContext.getApplicationUUID(), headers); + + sharedContext.setResponse(deleteResponse); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + Thread.sleep(3000); + } + + @Then("I find the apiUUID of the API created with the name {string}") + public void find_api_uuid_using_name(String apiName) throws Exception { + logger.info("Fetching the APIs"); + + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse appSearchResponse = sharedContext.getHttpClient().doGet(Utils.getAPISearchEndpoint(apiName), headers); + + sharedContext.setResponse(appSearchResponse); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + sharedContext.setApiUUID(Utils.extractAPIUUID(sharedContext.getResponseBody())); + Thread.sleep(3000); + } + + @When("I undeploy the selected API") + public void i_undeploy_the_api() throws Exception { + Map headers = new HashMap<>(); + headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, "Bearer " + sharedContext.getPublisherAccessToken()); + headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_API_HOST); + + HttpResponse response = sharedContext.getHttpClient().doDelete(Utils.getAPIUnDeployerURL(sharedContext.getApiUUID()), headers); + + sharedContext.setResponse(response); + sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + Thread.sleep(3000); + } } diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java index ddd50abab..bae41d5c3 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/BaseSteps.java @@ -43,6 +43,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.Header; +import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; @@ -50,6 +51,9 @@ import org.wso2.apk.integration.utils.Constants; import org.wso2.apk.integration.utils.Utils; import org.wso2.apk.integration.utils.clients.SimpleHTTPClient; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.ContentType; import java.io.IOException; import java.io.InputStream; @@ -130,6 +134,8 @@ public void sendHttpRequest(String httpMethod, String url, String body) throws I } else if (CurlOption.HttpMethod.POST.toString().toLowerCase().equals(httpMethod.toLowerCase())) { sharedContext.setResponse(httpClient.doPost(url, sharedContext.getHeaders(), body, null)); sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); + logger.info("Post Response: " + sharedContext.getResponse()); + logger.info("Post Response Body: " + sharedContext.getResponseBody()); } else if (CurlOption.HttpMethod.PUT.toString().toLowerCase().equals(httpMethod.toLowerCase())) { sharedContext.setResponse(httpClient.doPut(url, sharedContext.getHeaders(), body, null)); sharedContext.setResponseBody(SimpleHTTPClient.responseEntityBodyToString(sharedContext.getResponse())); @@ -347,7 +353,7 @@ public void iHaveValidPublisherAccessToken() throws Exception { headers.put(Constants.REQUEST_HEADERS.HOST, Constants.DEFAULT_IDP_HOST); headers.put(Constants.REQUEST_HEADERS.AUTHORIZATION, basicAuthHeader); - HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=password&username=admin&password=admin&scope=apim:api_view apim:api_create apim:api_publish", + HttpResponse httpResponse = httpClient.doPost(Utils.getTokenEndpointURL(), headers, "grant_type=password&username=admin&password=admin&scope=apim:api_view apim:api_create apim:api_publish apim:api_delete apim:api_manage apim:api_import_export", Constants.CONTENT_TYPES.APPLICATION_X_WWW_FORM_URLENCODED); logger.info("Response: " + httpResponse); sharedContext.setPublisherAccessToken(Utils.extractToken(httpResponse)); @@ -370,6 +376,12 @@ public void iHaveValidDevportalAccessToken() throws Exception { logger.info("Devportal Access Token: " + sharedContext.getDevportalAccessToken()); } + @Then("the response should be given as valid") + public void theResponseShouldBeGivenAs() throws IOException { + Boolean status = sharedContext.getDefinitionValidStatus(); + Assert.assertEquals(true, status,"Actual definition validation status: "+ status); + } + // @Given("I have a valid subscription without api deploy permission") // public void iHaveValidSubscriptionWithAPICreateScope() throws Exception { // diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java index acf781838..1f446fd6f 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/api/SharedContext.java @@ -44,6 +44,7 @@ public class SharedContext { private String consumerSecret; private String consumerKey; private String apiAccessToken; + private Boolean definitionValidStatus; private HashMap valueStore = new HashMap<>(); private HashMap headers = new HashMap<>(); @@ -203,4 +204,12 @@ public void setApiAccessToken(String apiAccessToken) { this.apiAccessToken = apiAccessToken; } + + public void setAPIDefinitionValidStatus(Boolean definitionValidStatus){ + this.definitionValidStatus = definitionValidStatus; + } + + public Boolean getDefinitionValidStatus(){ + return definitionValidStatus; + } } diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java index 07905c307..9f0da139c 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Constants.java @@ -33,6 +33,7 @@ public class Constants { public static final String SPACE_STRING = " "; public static final String SUBSCRIPTION_BASIC_AUTH_TOKEN = "Basic NDVmMWM1YzgtYTkyZS0xMWVkLWFmYTEtMDI0MmFjMTIwMDAyOjRmYmQ2MmVjLWE5MmUtMTFlZC1hZmExLTAyNDJhYzEyMDAwMg=="; + public static final String DEFAULT_APIM_HOST = "apim.wso2.com"; public class REQUEST_HEADERS { diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java index 6b040e946..64b616d0e 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/java/org/wso2/apk/integration/utils/Utils.java @@ -120,10 +120,24 @@ public static String getAPIRevisionDeploymentURL(String apiUUID, String revision + Constants.DEFAULT_API_DEPLOYER + "apis/" + apiUUID + "/deploy-revision?revisionId=" + revisionId; } - public static String getAPIUnDeployerURL() { + public static String getAPIUnDeployerURL(String apiID) { + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/" + apiID; + } + + public static String getGQLSchemaValidatorURL() { + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "apis/validate-graphql-schema"; + } + public static String getGQLImportAPIURL() { return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" - + Constants.DEFAULT_API_DEPLOYER + "apis/undeploy"; + + Constants.DEFAULT_API_DEPLOYER + "apis/import-graphql-schema"; + } + + public static String getAPISearchEndpoint(String queryValue) { + return "https://" + Constants.DEFAULT_API_HOST + ":" + Constants.DEFAULT_GW_PORT + "/" + + Constants.DEFAULT_API_DEPLOYER + "search?query=content:" + queryValue; } public static String extractID(String payload) throws IOException { @@ -298,4 +312,52 @@ public static String resolveVariables(String input, Map valueSto matcher.appendTail(resolvedString); return resolvedString.toString(); } + + public static Boolean extractValidStatus(String payload) throws IOException { + JSONParser parser = new JSONParser(); + try { + // Parse the JSON string + JSONObject jsonObject = (JSONObject) parser.parse(payload); + + // Get the value of the "isValid" attribute + Boolean validStatus = (Boolean) jsonObject.get("isValid"); + return validStatus; + } catch (ParseException e) { + throw new IOException("Error while parsing the JSON payload: " + e.getMessage()); + } + } + + public static String extractApplicationUUID(String payload) throws IOException { + JSONParser parser = new JSONParser(); + try { + JSONObject jsonObject = (JSONObject) parser.parse(payload); + long count = (long) jsonObject.get("count"); + if (count == 1) { + JSONArray list = (JSONArray) jsonObject.get("list"); + JSONObject applicationObj = (JSONObject) list.get(0); + String applicationId = (String) applicationObj.get("applicationId"); + return applicationId; + } + } catch (ParseException e) { + throw new IOException("Error while parsing the JSON payload: " + e.getMessage()); + } + return null; // Return null if count is not 1 + } + + public static String extractAPIUUID(String payload) throws IOException { + JSONParser parser = new JSONParser(); + try { + JSONObject jsonObject = (JSONObject) parser.parse(payload); + long count = (long) jsonObject.get("count"); + if (count == 1) { + JSONArray list = (JSONArray) jsonObject.get("list"); + JSONObject apiObj = (JSONObject) list.get(0); + String apiId = (String) apiObj.get("id"); + return apiId; + } + } catch (ParseException e) { + throw new IOException("Error while parsing the JSON payload: " + e.getMessage()); + } + return null; // Return null if count is not 1 + } } diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/schema_graphql.graphql b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/schema_graphql.graphql new file mode 100644 index 000000000..d8273eab3 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/definitions/schema_graphql.graphql @@ -0,0 +1,199 @@ +schema { + query: Query + mutation: Mutation + subscription: Subscription +} + +# The query type, represents all of the entry points into our object graph +type Query { + hero(episode: Episode): Character + reviews(episode: Episode!): [Review] + search(text: String): [SearchResult] + character(id: ID!): Character + droid(id: ID!): Droid + human(id: ID!): Human + allHumans(first: Int): [Human] + allDroids(first: Int): [Droid] + allCharacters(first: Int): [Character] + starship(id: ID!): Starship +} + +# The mutation type, represents all updates we can make to our data +type Mutation { + createReview(episode: Episode, review: ReviewInput!): Review +} + +# The subscription type, represents all subscriptions we can make to our data +type Subscription { + reviewAdded(episode: Episode): Review +} + +# The episodes in the Star Wars trilogy +enum Episode { + # Star Wars Episode IV: A New Hope, released in 1977. + NEWHOPE + + # Star Wars Episode V: The Empire Strikes Back, released in 1980. + EMPIRE + + # Star Wars Episode VI: Return of the Jedi, released in 1983. + JEDI + + # Star Wars Episode III: Revenge of the Sith, released in 2005 + SITH +} + +# A character from the Star Wars universe +interface Character { + # The ID of the character + id: ID! + + # The name of the character + name: String! + + # The friends of the character, or an empty list if they have none + friends: [Character] + + # The friends of the character exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this character appears in + appearsIn: [Episode]! +} + +# Units of height +enum LengthUnit { + # The standard unit around the world + METER + + # Primarily used in the United States + FOOT +} + +# A humanoid creature from the Star Wars universe +type Human implements Character { + # The ID of the human + id: ID! + + # What this human calls themselves + name: String! + + # The home planet of the human, or null if unknown + homePlanet: String + + # Height in the preferred unit, default is meters + height(unit: LengthUnit = METER): Float + + # Mass in kilograms, or null if unknown + mass: Float + + # This human's friends, or an empty list if they have none + friends: [Character] + + # The friends of the human exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this human appears in + appearsIn: [Episode]! + + # A list of starships this person has piloted, or an empty list if none + starships: [Starship] +} + +# An autonomous mechanical character in the Star Wars universe +type Droid implements Character { + # The ID of the droid + id: ID! + + # What others call this droid + name: String! + + # This droid's friends, or an empty list if they have none + friends: [Character] + + # The friends of the droid exposed as a connection with edges + friendsConnection(first: Int, after: ID): FriendsConnection! + + # The movies this droid appears in + appearsIn: [Episode]! + + # This droid's primary function + primaryFunction: String +} + +# A connection object for a character's friends +type FriendsConnection { + # The total number of friends + totalCount: Int + + # The edges for each of the character's friends. + edges: [FriendsEdge] + + # A list of the friends, as a convenience when edges are not needed. + friends: [Character] + + # Information for paginating this connection + pageInfo: PageInfo! +} + +# An edge object for a character's friends +type FriendsEdge { + # A cursor used for pagination + cursor: ID! + + # The character represented by this friendship edge + node: Character +} + +# Information for paginating this connection +type PageInfo { + startCursor: ID + endCursor: ID + hasNextPage: Boolean! +} + +# Represents a review for a movie +type Review { + # The movie + episode: Episode + + # The number of stars this review gave, 1-5 + stars: Int! + + # Comment about the movie + commentary: String +} + +# The input object sent when someone is creating a new review +input ReviewInput { + # 0-5 stars + stars: Int! + + # Comment about the movie, optional + commentary: String + + # Favorite color, optional + favorite_color: ColorInput +} + +# The input object sent when passing in a color +input ColorInput { + red: Int! + green: Int! + blue: Int! +} + +type Starship { + # The ID of the starship + id: ID! + + # The name of the starship + name: String! + + # Length of the starship, along the longest axis + length(unit: LengthUnit = METER): Float + + coordinates: [[Float!]!] +} + +union SearchResult = Human | Droid | Starship diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gqlPayload.json b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gqlPayload.json new file mode 100644 index 000000000..0cf90bf8e --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gqlPayload.json @@ -0,0 +1,194 @@ +{ + "name": "StarwarsAPI", + "version": "3.14", + "context": "/graphql", + "gatewayType": "Regular", + "policies": ["Unlimited"], + "operations": [ + { + "id": "0", + "target": "hero", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "1", + "target": "reviews", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "2", + "target": "search", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "3", + "target": "character", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "4", + "target": "droid", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "5", + "target": "human", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "6", + "target": "allHumans", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "7", + "target": "allDroids", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "8", + "target": "allCharacters", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "9", + "target": "starship", + "verb": "QUERY", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "10", + "target": "createReview", + "verb": "MUTATION", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + }, + { + "id": "11", + "target": "reviewAdded", + "verb": "SUBSCRIPTION", + "authType": "Any", + "throttlingPolicy": null, + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": null + } + ], + "endpointConfig": { + "endpoint_type": "http", + "sandbox_endpoints": { "url": "http://graphql-faker-service:9002/graphql" }, + "production_endpoints": { "url": "http://graphql-faker-service:9002/graphql" } + } +} diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gql_with_scopes.json b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gql_with_scopes.json new file mode 100644 index 000000000..18a2f9757 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/artifacts/payloads/gql_with_scopes.json @@ -0,0 +1,370 @@ +{ + "id": "6f742614-4547-471e-97f5-5cd3f724543f", + "name": "StarWarsAPI", + "description": null, + "context": "/graphql", + "version": "3.14", + "provider": "admin", + "lifeCycleStatus": "CREATED", + "wsdlInfo": null, + "wsdlUrl": null, + "responseCachingEnabled": false, + "cacheTimeout": 300, + "hasThumbnail": false, + "isDefaultVersion": false, + "isRevision": false, + "revisionedApiId": null, + "revisionId": 0, + "enableSchemaValidation": false, + "enableSubscriberVerification": false, + "type": "GRAPHQL", + "audience": null, + "transport": [ + "http", + "https" + ], + "tags": [], + "policies": [ + "Unlimited" + ], + "apiThrottlingPolicy": null, + "authorizationHeader": "Authorization", + "apiKeyHeader": "ApiKey", + "securityScheme": [ + "oauth_basic_auth_api_key_mandatory", + "oauth2" + ], + "maxTps": null, + "visibility": "PUBLIC", + "visibleRoles": [], + "visibleTenants": [], + "mediationPolicies": [], + "apiPolicies": null, + "subscriptionAvailability": "CURRENT_TENANT", + "subscriptionAvailableTenants": [], + "additionalProperties": [], + "additionalPropertiesMap": {}, + "monetization": null, + "accessControl": "NONE", + "accessControlRoles": [], + "businessInformation": { + "businessOwner": null, + "businessOwnerEmail": null, + "technicalOwner": null, + "technicalOwnerEmail": null + }, + "corsConfiguration": { + "corsConfigurationEnabled": false, + "accessControlAllowOrigins": [ + "*" + ], + "accessControlAllowCredentials": false, + "accessControlAllowHeaders": [ + "authorization", + "Access-Control-Allow-Origin", + "Content-Type", + "SOAPAction" + ], + "accessControlAllowMethods": [ + "GET", + "PUT", + "POST", + "DELETE", + "PATCH", + "OPTIONS" + ] + }, + "websubSubscriptionConfiguration": { + "enable": false, + "secret": "", + "signingAlgorithm": "SHA1", + "signatureHeader": "x-hub-signature" + }, + "workflowStatus": null, + "createdTime": "1710911652000", + "lastUpdatedTimestamp": "1710912171000", + "lastUpdatedTime": "2024-03-20 05:22:51.0", + "endpointConfig": { + "endpoint_type": "http", + "sandbox_endpoints": { + "url": "http://graphql-faker-service:9002/graphql" + }, + "production_endpoints": { + "url": "http://graphql-faker-service:9002/graphql" + } + }, + "endpointImplementationType": "ENDPOINT", + "scopes": [ + { + "scope": { + "id": null, + "name": "query:hero", + "displayName": "query:hero", + "description": "Query your favorite starwars hero", + "bindings": [], + "usageCount": null + }, + "shared": false + }, + { + "scope": { + "id": null, + "name": "query:human", + "displayName": "query:human", + "description": "Query the human characters featured in startwars", + "bindings": [], + "usageCount": null + }, + "shared": false + } + ], + "operations": [ + { + "id": "", + "target": "allCharacters", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "allDroids", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "allHumans", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "character", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "createReview", + "verb": "MUTATION", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "droid", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "hero", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [ + "query:hero" + ], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "human", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "reviewAdded", + "verb": "SUBSCRIPTION", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "reviews", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "search", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + }, + { + "id": "", + "target": "starship", + "verb": "QUERY", + "authType": "Application & Application User", + "throttlingPolicy": "Unlimited", + "scopes": [], + "usedProductIds": [], + "amznResourceName": null, + "amznResourceTimeout": null, + "amznResourceContentEncode": null, + "payloadSchema": null, + "uriMapping": null, + "operationPolicies": { + "request": [], + "response": [], + "fault": [] + } + } + ], + "threatProtectionPolicies": null, + "categories": [], + "keyManagers": [ + "all" + ], + "serviceInfo": null, + "advertiseInfo": { + "advertised": false, + "apiExternalProductionEndpoint": null, + "apiExternalSandboxEndpoint": null, + "originalDevPortalUrl": null, + "apiOwner": "admin", + "vendor": "WSO2" + }, + "gatewayVendor": "wso2", + "gatewayType": "wso2/synapse", + "asyncTransportProtocols": [] +} \ No newline at end of file diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature index b97e0ba25..2b8797599 100644 --- a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/Deployment.feature @@ -14,9 +14,9 @@ Feature: API Deployment And make the Change Lifecycle request Then the response status code should be 200 And I have a valid Devportal access token - And make the Application Creation request + And make the Application Creation request with the name "SampleApp" Then the response status code should be 201 - And the response body should contain "PetstoreApp" + And the response body should contain "SampleApp" And I have a KeyManager And make the Generate Keys request Then the response status code should be 200 @@ -24,7 +24,7 @@ Feature: API Deployment And the response body should contain "consumerSecret" And make the Subscription request Then the response status code should be 201 - And the response body should contain "Gold" + And the response body should contain "Unlimited" And I get oauth keys for application Then the response status code should be 200 And make the Access Token Generation request @@ -33,13 +33,66 @@ Feature: API Deployment And I send "GET" request to "https://default.gw.wso2.com:9095/petstore/1.0.0/pet/5" with body "" And I eventually receive 200 response code, not accepting |429| + + Scenario: Undeploying an already existing REST API + And I have a DCR application + And I have a valid Devportal access token + Then I delete the application "SampleApp" from devportal + Then the response status code should be 200 + And I have a valid Publisher access token + Then I find the apiUUID of the API created with the name "SwaggerPetstore" + Then I undeploy the selected API + Then the response status code should be 200 + And I send "GET" request to "https://default.gw.wso2.com:9095/petstore/1.0.0/pet/5" with body "" + And I eventually receive 404 response code, not accepting + |200| -# Scenario Outline: Undeploy an 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 + Scenario: Deploying a GraphQL API + And I have a DCR application + And I have a valid Publisher access token + When the definition file "artifacts/definitions/schema_graphql.graphql" + Given a valid graphql definition file + Then the response should be given as valid + When I use the Payload file "artifacts/payloads/gqlPayload.json" + Then I make the import GraphQLAPI Creation request + Then the response status code should be 201 + And the response body should contain "StarwarsAPI" + And make the API Revision Deployment request + Then the response status code should be 201 + And make the Change Lifecycle request + Then the response status code should be 200 + And I have a valid Devportal access token + And make the Application Creation request with the name "TestApp" + Then the response status code should be 201 + And the response body should contain "TestApp" + And I have a KeyManager + And make the Generate Keys request + Then the response status code should be 200 + And the response body should contain "consumerKey" + And the response body should contain "consumerSecret" + And make the Subscription request + Then the response status code should be 201 + And the response body should contain "Unlimited" + And I get oauth keys for application + Then the response status code should be 200 + And make the Access Token Generation request + Then the response status code should be 200 + And the response body should contain "accessToken" + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ hero { name } }\"}" + Then the response status code should be 200 + And I eventually receive 200 response code, not accepting + | 404 | + | 401 | -# Examples: -# | apiID | expectedStatusCode | -# | cors-api-adff3dbc-2787-11ee-be56-0242ac120002 | 202 | + Scenario: Undeploying an already existing GraphQL API + And I have a DCR application + And I have a valid Devportal access token + Then I delete the application "TestApp" from devportal + Then the response status code should be 200 + And I have a valid Publisher access token + Then I find the apiUUID of the API created with the name "StarwarsAPI" + Then I undeploy the selected API + Then the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ hero { name } }\"}" + And I eventually receive 404 response code, not accepting + |200| \ No newline at end of file diff --git a/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/ScopesEnabled.feature b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/ScopesEnabled.feature new file mode 100644 index 000000000..83bdafdf8 --- /dev/null +++ b/test/apim-apk-agent-test/cucumber-tests/src/test/resources/tests/api/ScopesEnabled.feature @@ -0,0 +1,105 @@ +Feature: Invoking APIs with scopes enabled + Background: + Given The system is ready + Scenario: Create API and enable scopes for resources and invoke them using keys with and without proper scopes + And I have a DCR application + And I have a valid Publisher access token + When I use the Payload file "artifacts/payloads/api1.json" + And I use the OAS URL "https://petstore3.swagger.io/api/v3/openapi.json" + And make the import API Creation request + Then the response status code should be 201 + And the response body should contain "SwaggerPetstore" + And make the API Revision Deployment request + Then the response status code should be 201 + And make the Change Lifecycle request + Then the response status code should be 200 + And I have a valid Devportal access token + And make the Application Creation request with the name "SampleApp" + Then the response status code should be 201 + And the response body should contain "SampleApp" + And I have a KeyManager + And make the Generate Keys request + Then the response status code should be 200 + And the response body should contain "consumerKey" + And the response body should contain "consumerSecret" + And make the Subscription request + Then the response status code should be 201 + And the response body should contain "Unlimited" + And I get oauth keys for application + Then the response status code should be 200 + And make the Access Token Generation request + Then the response status code should be 200 + And the response body should contain "accessToken" + And I send "GET" request to "https://default.gw.wso2.com:9095/petstore/1.0.0/pet/5" with body "" + And I eventually receive 200 response code, not accepting + |429| + Then I make Access Token Generation request without scopes + Then the response status code should be 200 + And the response body should contain "accessToken" + And I send "GET" request to "https://default.gw.wso2.com:9095/petstore/1.0.0/pet/5" with body "" + Then the response status code should be 403 + + Scenario: Undeploy the created REST API + And I have a DCR application + And I have a valid Devportal access token + Then I delete the application "SampleApp" from devportal + Then the response status code should be 200 + And I have a valid Publisher access token + Then I find the apiUUID of the API created with the name "SwaggerPetstore" + Then I undeploy the selected API + Then the response status code should be 200 + + Scenario: Deploying a GraphQL API with scopes and invoking it with and without scopes + And I have a DCR application + And I have a valid Publisher access token + When the definition file "artifacts/definitions/schema_graphql.graphql" + Given a valid graphql definition file + Then the response should be given as valid + When I use the Payload file "artifacts/payloads/gql_with_scopes.json" + Then I make the import GraphQLAPI Creation request + Then the response status code should be 201 + And the response body should contain "StarWarsAPI" + And make the API Revision Deployment request + Then the response status code should be 201 + And make the Change Lifecycle request + Then the response status code should be 200 + And I have a valid Devportal access token + And make the Application Creation request with the name "TestApp" + Then the response status code should be 201 + And the response body should contain "TestApp" + And I have a KeyManager + And make the Generate Keys request + Then the response status code should be 200 + And the response body should contain "consumerKey" + And the response body should contain "consumerSecret" + And make the Subscription request + Then the response status code should be 201 + And the response body should contain "Unlimited" + And I get oauth keys for application + Then the response status code should be 200 + And make the Access Token Generation request + Then the response status code should be 200 + And the response body should contain "accessToken" + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ hero { name } }\"}" + Then the response status code should be 200 + And I eventually receive 200 response code, not accepting + | 404 | + | 401 | + Then I make Access Token Generation request without scopes + Then the response status code should be 200 + And the response body should contain "accessToken" + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ hero { name } }\"}" + Then the response status code should be 403 + + Scenario: Undeploying an already existing GraphQL API + And I have a DCR application + And I have a valid Devportal access token + Then I delete the application "TestApp" from devportal + Then the response status code should be 200 + And I have a valid Publisher access token + Then I find the apiUUID of the API created with the name "StarWarsAPI" + Then I undeploy the selected API + Then the response status code should be 200 + And I send "POST" request to "https://default.gw.wso2.com:9095/graphql/3.14" with body "{\"query\":\"{ hero { name } }\"}" + And I eventually receive 404 response code, not accepting + |200| \ No newline at end of file