From adad62718648be6518cb035262ba4eb58ab285a7 Mon Sep 17 00:00:00 2001 From: Moiz Arafat Date: Thu, 11 Mar 2021 15:27:21 -0500 Subject: [PATCH] refactor async IT and Fix alias in JSON format (#1870) Co-authored-by: Aaron Klish --- .../export/formatter/JSONExportFormatter.java | 4 +- .../async/integration/tests/AsyncApiIT.java | 194 +++++++++++ .../async/integration/tests/AsyncIT.java | 283 +++------------- .../integration/tests/TableExportIT.java | 314 +++++++++--------- .../elide/initialization/IntegrationTest.java | 4 +- 5 files changed, 406 insertions(+), 393 deletions(-) create mode 100644 elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncApiIT.java diff --git a/elide-async/src/main/java/com/yahoo/elide/async/export/formatter/JSONExportFormatter.java b/elide-async/src/main/java/com/yahoo/elide/async/export/formatter/JSONExportFormatter.java index e5c347c285..325d02806d 100644 --- a/elide-async/src/main/java/com/yahoo/elide/async/export/formatter/JSONExportFormatter.java +++ b/elide-async/src/main/java/com/yahoo/elide/async/export/formatter/JSONExportFormatter.java @@ -72,7 +72,9 @@ private static Map getAttributes(PersistentResource resource) { final Set attrFields = resource.getRequestScope().getEntityProjection().getAttributes(); for (Attribute field : attrFields) { - attributes.put(field.getName(), resource.getAttribute(field)); + String alias = field.getAlias(); + String fieldName = alias != null && !alias.isEmpty() ? alias : field.getName(); + attributes.put(fieldName, resource.getAttribute(field)); } return attributes; } diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncApiIT.java b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncApiIT.java new file mode 100644 index 0000000000..c756a35b7a --- /dev/null +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncApiIT.java @@ -0,0 +1,194 @@ +/* + * Copyright 2021, Yahoo Inc. + * Licensed under the Apache License, Version 2.0 + * See LICENSE file in project root for terms. + */ +package com.yahoo.elide.async.integration.tests; + +import static com.yahoo.elide.Elide.JSONAPI_CONTENT_TYPE; +import static com.yahoo.elide.test.jsonapi.JsonApiDSL.attr; +import static com.yahoo.elide.test.jsonapi.JsonApiDSL.attributes; +import static com.yahoo.elide.test.jsonapi.JsonApiDSL.datum; +import static com.yahoo.elide.test.jsonapi.JsonApiDSL.resource; +import static com.yahoo.elide.test.jsonapi.JsonApiDSL.type; +import static io.restassured.RestAssured.given; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import com.yahoo.elide.async.integration.tests.framework.AsyncIntegrationTestApplicationResourceConfig; +import com.yahoo.elide.core.datastore.DataStore; +import com.yahoo.elide.core.datastore.test.DataStoreTestHarness; +import com.yahoo.elide.core.exceptions.HttpStatus; +import com.yahoo.elide.initialization.IntegrationTest; +import com.yahoo.elide.jsonapi.resources.JsonApiEndpoint; +import com.yahoo.elide.test.jsonapi.elements.Resource; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import org.junit.jupiter.api.BeforeEach; + +import io.restassured.response.Response; +import lombok.Getter; + +import java.util.Map; +import java.util.concurrent.Executors; + +import javax.ws.rs.core.MediaType; + +/** + * Parent class with common code for AsyncIT and TableExportIT. + */ +public abstract class AsyncApiIT extends IntegrationTest { + @Getter private Integer port; + private String apiPath; + + private static final Resource ENDERS_GAME = resource( + type("book"), + attributes( + attr("title", "Ender's Game"), + attr("genre", "Science Fiction"), + attr("language", "English") + ) + ); + + private static final Resource GAME_OF_THRONES = resource( + type("book"), + attributes( + attr("title", "Song of Ice and Fire"), + attr("genre", "Mythology Fiction"), + attr("language", "English") + ) + ); + + private static final Resource FOR_WHOM_THE_BELL_TOLLS = resource( + type("book"), + attributes( + attr("title", "For Whom the Bell Tolls"), + attr("genre", "Literary Fiction"), + attr("language", "English") + ) + ); + + public AsyncApiIT(String apiPath) { + super(AsyncIntegrationTestApplicationResourceConfig.class, JsonApiEndpoint.class.getPackage().getName()); + + this.port = super.getRestAssuredPort(); + this.apiPath = apiPath; + } + + @Override + public void modifyServletContextHandler() { + // Set Attributes to be fetched in AsyncIntegrationTestApplicationResourceConfig + this.servletContextHandler.setAttribute(AsyncIntegrationTestApplicationResourceConfig.ASYNC_EXECUTOR_ATTR, Executors.newFixedThreadPool(5)); + } + + @Override + protected DataStoreTestHarness createHarness() { + DataStoreTestHarness dataStoreTestHarness = super.createHarness(); + return new DataStoreTestHarness() { + @Override + public DataStore getDataStore() { + return new AsyncDelayDataStore(dataStoreTestHarness.getDataStore()); + } + @Override + public void cleanseTestData() { + dataStoreTestHarness.cleanseTestData(); + } + }; + } + /** + * Creates test data for all tests. + */ + @BeforeEach + public void init() { + //Create Book: Ender's Game + given() + .contentType(JSONAPI_CONTENT_TYPE) + .accept(JSONAPI_CONTENT_TYPE) + .body( + datum(ENDERS_GAME).toJSON() + ) + .post("/book") + .then() + .statusCode(HttpStatus.SC_CREATED); + + given() + .contentType(JSONAPI_CONTENT_TYPE) + .accept(JSONAPI_CONTENT_TYPE) + .body( + datum(GAME_OF_THRONES).toJSON() + ) + .post("/book") + .then() + .statusCode(HttpStatus.SC_CREATED); + + given() + .contentType(JSONAPI_CONTENT_TYPE) + .accept(JSONAPI_CONTENT_TYPE) + .body( + datum(FOR_WHOM_THE_BELL_TOLLS).toJSON() + ) + .post("/book") + .then() + .statusCode(HttpStatus.SC_CREATED); + } + + public Response getJSONAPIResponse(String id) throws InterruptedException { + Response response = null; + int i = 0; + while (i < 1000) { + Thread.sleep(10); + response = given() + .accept("application/vnd.api+json") + .get("/" + apiPath + "/" + id); + + String outputResponse = response.jsonPath().getString("data.attributes.status"); + + // If Async API is completed + if (outputResponse.equals("COMPLETE")) { + break; + } + assertEquals("PROCESSING", outputResponse, "Async API Request has failed."); + i++; + assertNotEquals(1000, i, "Async API Request not completed."); + } + + return response; + } + + public String getGraphQLResponse(String id, String additionalResultColumns) throws InterruptedException { + String responseGraphQL = null; + int i = 0; + while (i < 1000) { + Thread.sleep(10); + responseGraphQL = given() + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .body("{\"query\":\"{ " + apiPath + "(ids: [\\\"" + id + "\\\"]) " + + "{ edges { node { id queryType status result " + + "{ " + additionalResultColumns + " httpStatus recordCount } } } } }\"," + + "\"variables\":null}") + .post("/graphQL") + .asString(); + // If Async API Request is created and completed + if (responseGraphQL.contains("\"status\":\"COMPLETE\"")) { + break; + } + assertTrue(responseGraphQL.contains("\"status\":\"PROCESSING\""), "Async API Request has failed."); + i++; + assertNotEquals(1000, i, "Async API Request not completed."); + } + + return responseGraphQL; + } + + public JsonNode toJsonNode(String query, Map variables) { + ObjectNode graphqlNode = JsonNodeFactory.instance.objectNode(); + graphqlNode.put("query", query); + if (variables != null) { + graphqlNode.set("variables", mapper.valueToTree(variables)); + } + return graphqlNode; + } +} diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncIT.java b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncIT.java index 1d73022ee2..ee9fe9cf8b 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncIT.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/AsyncIT.java @@ -18,7 +18,6 @@ import static com.yahoo.elide.test.jsonapi.JsonApiDSL.attr; import static com.yahoo.elide.test.jsonapi.JsonApiDSL.attributes; import static com.yahoo.elide.test.jsonapi.JsonApiDSL.data; -import static com.yahoo.elide.test.jsonapi.JsonApiDSL.datum; import static com.yahoo.elide.test.jsonapi.JsonApiDSL.id; import static com.yahoo.elide.test.jsonapi.JsonApiDSL.resource; import static com.yahoo.elide.test.jsonapi.JsonApiDSL.type; @@ -28,8 +27,6 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import com.yahoo.elide.Elide; import com.yahoo.elide.ElideResponse; @@ -37,23 +34,15 @@ import com.yahoo.elide.async.integration.tests.framework.AsyncIntegrationTestApplicationResourceConfig; import com.yahoo.elide.async.models.QueryType; import com.yahoo.elide.core.audit.TestAuditLogger; -import com.yahoo.elide.core.datastore.DataStore; import com.yahoo.elide.core.datastore.DataStoreTransaction; -import com.yahoo.elide.core.datastore.test.DataStoreTestHarness; import com.yahoo.elide.core.dictionary.EntityDictionary; import com.yahoo.elide.core.exceptions.HttpStatus; import com.yahoo.elide.core.security.User; -import com.yahoo.elide.initialization.IntegrationTest; -import com.yahoo.elide.jsonapi.resources.JsonApiEndpoint; import com.yahoo.elide.jsonapi.resources.SecurityContextUser; import com.yahoo.elide.test.graphql.EnumFieldSerializer; -import com.yahoo.elide.test.jsonapi.elements.Resource; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -62,15 +51,13 @@ import java.io.IOException; import java.security.Principal; -import java.util.Map; -import java.util.concurrent.Executors; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.SecurityContext; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class AsyncIT extends IntegrationTest { +public class AsyncIT extends AsyncApiIT { @Data private class AsyncQuery { @@ -86,93 +73,12 @@ private class AsyncQuery { private String status; } - private static final Resource ENDERS_GAME = resource( - type("book"), - attributes( - attr("title", "Ender's Game"), - attr("genre", "Science Fiction"), - attr("language", "English") - ) - ); - - private static final Resource GAME_OF_THRONES = resource( - type("book"), - attributes( - attr("title", "Song of Ice and Fire"), - attr("genre", "Mythology Fiction"), - attr("language", "English") - ) - ); - - private static final Resource FOR_WHOM_THE_BELL_TOLLS = resource( - type("book"), - attributes( - attr("title", "For Whom the Bell Tolls"), - attr("genre", "Literary Fiction"), - attr("language", "English") - ) - ); - public AsyncIT() { - super(AsyncIntegrationTestApplicationResourceConfig.class, JsonApiEndpoint.class.getPackage().getName()); - } - - @Override - public void modifyServletContextHandler() { - // Set Attributes to be fetched in AsyncIntegrationTestApplicationResourceConfig - this.servletContextHandler.setAttribute(AsyncIntegrationTestApplicationResourceConfig.ASYNC_EXECUTOR_ATTR, Executors.newFixedThreadPool(5)); - } - - @Override - protected DataStoreTestHarness createHarness() { - DataStoreTestHarness dataStoreTestHarness = super.createHarness(); - return new DataStoreTestHarness() { - @Override - public DataStore getDataStore() { - return new AsyncDelayDataStore(dataStoreTestHarness.getDataStore(), 5000); - } - @Override - public void cleanseTestData() { - dataStoreTestHarness.cleanseTestData(); - } - }; + super("asyncQuery"); } - /** - * Creates test data for all tests. - */ - @BeforeEach - public void init() { - //Create Book: Ender's Game - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(ENDERS_GAME).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); - - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(GAME_OF_THRONES).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); - - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(FOR_WHOM_THE_BELL_TOLLS).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); + public String getGraphQLResponse(String id) throws InterruptedException { + return super.getGraphQLResponse(id, "responseBody contentLength"); } /** @@ -211,40 +117,23 @@ public void jsonApiHappyPath1() throws InterruptedException { .body("data.attributes.result.responseBody", nullValue()) .body("data.attributes.result.httpStatus", nullValue()); - int i = 0; - while (i < 1000) { - Thread.sleep(10); - Response response = given() - .accept("application/vnd.api+json") - .get("/asyncQuery/edc4a871-dff2-4054-804e-d80075cf830e"); - - String outputResponse = response.jsonPath().getString("data.attributes.status"); - - // If Async Query is created and completed - if (outputResponse.equals("COMPLETE")) { - - // Validate AsyncQuery Response - response - .then() - .statusCode(HttpStatus.SC_OK) - .body("data.id", equalTo("edc4a871-dff2-4054-804e-d80075cf830e")) - .body("data.type", equalTo("asyncQuery")) - .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) - .body("data.attributes.status", equalTo("COMPLETE")) - .body("data.attributes.result.contentLength", notNullValue()) - .body("data.attributes.result.recordCount", equalTo(3)) - .body("data.attributes.result.responseBody", equalTo("{\"data\":" - + "[{\"type\":\"book\",\"id\":\"3\",\"attributes\":{\"title\":\"For Whom the Bell Tolls\"}}" - + ",{\"type\":\"book\",\"id\":\"2\",\"attributes\":{\"title\":\"Song of Ice and Fire\"}}," - + "{\"type\":\"book\",\"id\":\"1\",\"attributes\":{\"title\":\"Ender's Game\"}}]}")) - .body("data.attributes.result.httpStatus", equalTo(200)); - - break; - } - assertEquals("PROCESSING", outputResponse, "Async Query has failed."); - i++; - assertNotEquals(1000, i, "Async Query not completed."); - } + Response response = getJSONAPIResponse("edc4a871-dff2-4054-804e-d80075cf830e"); + + // Validate AsyncQuery Response + response + .then() + .statusCode(HttpStatus.SC_OK) + .body("data.id", equalTo("edc4a871-dff2-4054-804e-d80075cf830e")) + .body("data.type", equalTo("asyncQuery")) + .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) + .body("data.attributes.status", equalTo("COMPLETE")) + .body("data.attributes.result.contentLength", notNullValue()) + .body("data.attributes.result.recordCount", equalTo(3)) + .body("data.attributes.result.responseBody", equalTo("{\"data\":" + + "[{\"type\":\"book\",\"id\":\"3\",\"attributes\":{\"title\":\"For Whom the Bell Tolls\"}}" + + ",{\"type\":\"book\",\"id\":\"2\",\"attributes\":{\"title\":\"Song of Ice and Fire\"}}," + + "{\"type\":\"book\",\"id\":\"1\",\"attributes\":{\"title\":\"Ender's Game\"}}]}")) + .body("data.attributes.result.httpStatus", equalTo(200)); } /** @@ -337,35 +226,16 @@ public void graphQLHappyPath1() throws InterruptedException { .statusCode(org.apache.http.HttpStatus.SC_OK) .body(equalTo(expectedResponse)); - int i = 0; - while (i < 1000) { - Thread.sleep(10); - String responseGraphQL = given() - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .body("{\"query\":\"{ asyncQuery(ids: [\\\"edc4a871-dff2-4054-804e-d80075cf828e\\\"]) " - + "{ edges { node { id queryType status result " - + "{ responseBody httpStatus contentLength } } } } }\"," - + "\"variables\":null}") - .post("/graphQL") - .asString(); - // If Async Query is created and completed - if (responseGraphQL.contains("\"status\":\"COMPLETE\"")) { - - expectedResponse = "{\"data\":{\"asyncQuery\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cf828e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," - + "\"result\":{\"responseBody\":\"{\\\"data\\\":{\\\"book\\\":{\\\"edges\\\":[{\\\"node\\\":{\\\"id\\\":\\\"1\\\",\\\"title\\\":\\\"Ender's Game\\\"}}," - + "{\\\"node\\\":{\\\"id\\\":\\\"2\\\",\\\"title\\\":\\\"Song of Ice and Fire\\\"}}," - + "{\\\"node\\\":{\\\"id\\\":\\\"3\\\",\\\"title\\\":\\\"For Whom the Bell Tolls\\\"}}]}}}\"," - + "\"httpStatus\":200,\"contentLength\":177}}}]}}}"; - - assertEquals(expectedResponse, responseGraphQL); - break; - } - assertTrue(responseGraphQL.contains("\"status\":\"PROCESSING\""), "Async Query has failed."); - i++; - assertNotEquals(1000, i, "Async Query not completed."); - } + String responseGraphQL = getGraphQLResponse("edc4a871-dff2-4054-804e-d80075cf828e"); + expectedResponse = "{\"data\":{\"asyncQuery\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cf828e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," + + "\"result\":{\"responseBody\":\"{\\\"data\\\":{\\\"book\\\":{\\\"edges\\\":[{\\\"node\\\":{\\\"id\\\":\\\"1\\\",\\\"title\\\":\\\"Ender's Game\\\"}}," + + "{\\\"node\\\":{\\\"id\\\":\\\"2\\\",\\\"title\\\":\\\"Song of Ice and Fire\\\"}}," + + "{\\\"node\\\":{\\\"id\\\":\\\"3\\\",\\\"title\\\":\\\"For Whom the Bell Tolls\\\"}}]}}}\"," + + "\"contentLength\":177,\"httpStatus\":200,\"recordCount\":3}}}]}}}"; + + assertEquals(expectedResponse, responseGraphQL); } + /** * Test for a GraphQL query as a Async Request with asyncAfterSeconds value set to 7. * Happy Path Test Scenario 2 @@ -502,35 +372,18 @@ public void jsonApiUnknownRequestTests() throws InterruptedException { .then() .statusCode(org.apache.http.HttpStatus.SC_CREATED); - int i = 0; - while (i < 1000) { - Thread.sleep(10); - Response response = given() - .accept("application/vnd.api+json") - .get("/asyncQuery/ba31ca4e-ed8f-4be0-a0f3-12088fa9263b"); - - String outputResponse = response.jsonPath().getString("data.attributes.status"); - - // If Async Query is created and completed then validate results - if (outputResponse.equals("COMPLETE")) { - // Validate AsyncQuery Response - response - .then() - .statusCode(HttpStatus.SC_OK) - .body("data.id", equalTo("ba31ca4e-ed8f-4be0-a0f3-12088fa9263b")) - .body("data.type", equalTo("asyncQuery")) - .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) - .body("data.attributes.status", equalTo("COMPLETE")) - .body("data.attributes.result.contentLength", notNullValue()) - .body("data.attributes.result.responseBody", equalTo("{\"errors\":[{\"detail\":\"Unknown collection group\"}]}")) - .body("data.attributes.result.httpStatus", equalTo(404)); - - break; - } - assertEquals("PROCESSING", outputResponse, "Async Query has failed."); - i++; - assertNotEquals(1000, i, "Async Query not completed."); - } + Response response = getJSONAPIResponse("ba31ca4e-ed8f-4be0-a0f3-12088fa9263b"); + + response + .then() + .statusCode(HttpStatus.SC_OK) + .body("data.id", equalTo("ba31ca4e-ed8f-4be0-a0f3-12088fa9263b")) + .body("data.type", equalTo("asyncQuery")) + .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) + .body("data.attributes.status", equalTo("COMPLETE")) + .body("data.attributes.result.contentLength", notNullValue()) + .body("data.attributes.result.responseBody", equalTo("{\"errors\":[{\"detail\":\"Unknown collection group\"}]}")) + .body("data.attributes.result.httpStatus", equalTo(404)); } /** @@ -601,36 +454,19 @@ public void noReadEntityTests() throws InterruptedException { .then() .statusCode(org.apache.http.HttpStatus.SC_CREATED); - int i = 0; - while (i < 1000) { - Thread.sleep(10); - Response response = given() - .accept("application/vnd.api+json") - .get("/asyncQuery/0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e"); - - String outputResponse = response.jsonPath().getString("data.attributes.status"); - - // If Async Query is created and completed - if (outputResponse.equals("COMPLETE")) { - // Validate AsyncQuery Response - response - .then() - .statusCode(HttpStatus.SC_OK) - .body("data.id", equalTo("0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e")) - .body("data.type", equalTo("asyncQuery")) - .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) - .body("data.attributes.status", equalTo("COMPLETE")) - .body("data.attributes.result.recordCount", equalTo(0)) - .body("data.attributes.result.contentLength", notNullValue()) - .body("data.attributes.result.responseBody", equalTo("{\"data\":[]}")) - .body("data.attributes.result.httpStatus", equalTo(200)); - - break; - } - assertEquals("PROCESSING", outputResponse, "Async Query has failed."); - i++; - assertNotEquals(1000, i, "Async Query not completed."); - } + Response response = getJSONAPIResponse("0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e"); + // Validate AsyncQuery Response + response + .then() + .statusCode(HttpStatus.SC_OK) + .body("data.id", equalTo("0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e")) + .body("data.type", equalTo("asyncQuery")) + .body("data.attributes.queryType", equalTo("JSONAPI_V1_0")) + .body("data.attributes.status", equalTo("COMPLETE")) + .body("data.attributes.result.recordCount", equalTo(0)) + .body("data.attributes.result.contentLength", notNullValue()) + .body("data.attributes.result.responseBody", equalTo("{\"data\":[]}")) + .body("data.attributes.result.httpStatus", equalTo(200)); } /** @@ -747,13 +583,4 @@ public void asyncAfterBeyondMax() throws InterruptedException { .body(equalTo(expected)); } - - private JsonNode toJsonNode(String query, Map variables) { - ObjectNode graphqlNode = JsonNodeFactory.instance.objectNode(); - graphqlNode.put("query", query); - if (variables != null) { - graphqlNode.set("variables", mapper.valueToTree(variables)); - } - return graphqlNode; - } } diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/TableExportIT.java b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/TableExportIT.java index 420833c435..79a3195993 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/TableExportIT.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/TableExportIT.java @@ -28,7 +28,6 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import com.yahoo.elide.Elide; import com.yahoo.elide.ElideResponse; @@ -37,25 +36,18 @@ import com.yahoo.elide.async.models.QueryType; import com.yahoo.elide.async.models.ResultType; import com.yahoo.elide.core.audit.TestAuditLogger; -import com.yahoo.elide.core.datastore.DataStore; import com.yahoo.elide.core.datastore.DataStoreTransaction; -import com.yahoo.elide.core.datastore.test.DataStoreTestHarness; import com.yahoo.elide.core.dictionary.EntityDictionary; import com.yahoo.elide.core.exceptions.HttpStatus; import com.yahoo.elide.core.security.User; -import com.yahoo.elide.initialization.IntegrationTest; -import com.yahoo.elide.jsonapi.resources.JsonApiEndpoint; import com.yahoo.elide.jsonapi.resources.SecurityContextUser; import com.yahoo.elide.test.graphql.EnumFieldSerializer; import com.yahoo.elide.test.jsonapi.elements.Resource; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.node.JsonNodeFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; import org.eclipse.jetty.servlet.ServletHolder; import org.glassfish.jersey.servlet.ServletContainer; -import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; @@ -65,67 +57,35 @@ import java.io.IOException; import java.nio.file.Files; import java.security.Principal; -import java.util.Map; -import java.util.concurrent.Executors; + import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedHashMap; import javax.ws.rs.core.SecurityContext; @TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TableExportIT extends IntegrationTest { - private Integer port; +public class TableExportIT extends AsyncApiIT { @Data private class TableExport { private String id; private String query; - @JsonSerialize(using = EnumFieldSerializer.class, as = String.class) private String queryType; private Integer asyncAfterSeconds; @JsonSerialize(using = EnumFieldSerializer.class, as = String.class) private String resultType; - @JsonSerialize(using = EnumFieldSerializer.class, as = String.class) private String status; } - private static final Resource ENDERS_GAME = resource( - type("book"), - attributes( - attr("title", "Ender's Game"), - attr("genre", "Science Fiction"), - attr("language", "English") - ) - ); - - private static final Resource GAME_OF_THRONES = resource( - type("book"), - attributes( - attr("title", "Song of Ice and Fire"), - attr("genre", "Mythology Fiction"), - attr("language", "English") - ) - ); - - private static final Resource FOR_WHOM_THE_BELL_TOLLS = resource( - type("book"), - attributes( - attr("title", "For Whom the Bell Tolls"), - attr("genre", "Literary Fiction"), - attr("language", "English") - ) - ); - public TableExportIT() { - super(AsyncIntegrationTestApplicationResourceConfig.class, JsonApiEndpoint.class.getPackage().getName()); - - this.port = super.getPort(); + super("tableExport"); } @Override public void modifyServletContextHandler() { + super.modifyServletContextHandler(); // Initialize Export End Point ServletHolder exportServlet = servletContextHandler.addServlet(ServletContainer.class, "/export/*"); exportServlet.setInitOrder(3); @@ -134,7 +94,6 @@ public void modifyServletContextHandler() { exportServlet.setInitParameter("javax.ws.rs.Application", AsyncIntegrationTestApplicationResourceConfig.class.getName()); // Set Attributes to be fetched in AsyncIntegrationTestApplicationResourceConfig - this.servletContextHandler.setAttribute(AsyncIntegrationTestApplicationResourceConfig.ASYNC_EXECUTOR_ATTR, Executors.newFixedThreadPool(5)); try { this.servletContextHandler.setAttribute(AsyncIntegrationTestApplicationResourceConfig.STORAGE_DESTINATION_ATTR, Files.createTempDirectory("asyncIT")); } catch (IOException e) { @@ -142,104 +101,8 @@ public void modifyServletContextHandler() { } } - @Override - protected DataStoreTestHarness createHarness() { - DataStoreTestHarness dataStoreTestHarness = super.createHarness(); - return new DataStoreTestHarness() { - @Override - public DataStore getDataStore() { - return new AsyncDelayDataStore(dataStoreTestHarness.getDataStore()); - } - @Override - public void cleanseTestData() { - dataStoreTestHarness.cleanseTestData(); - } - }; - } - /** - * Creates test data for all tests. - */ - @BeforeEach - public void init() { - //Create Book: Ender's Game - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(ENDERS_GAME).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); - - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(GAME_OF_THRONES).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); - - given() - .contentType(JSONAPI_CONTENT_TYPE) - .accept(JSONAPI_CONTENT_TYPE) - .body( - datum(FOR_WHOM_THE_BELL_TOLLS).toJSON() - ) - .post("/book") - .then() - .statusCode(HttpStatus.SC_CREATED); - } - - private Response getJSONAPIResponse(String id) throws InterruptedException { - Response response = null; - int i = 0; - while (i < 1000) { - Thread.sleep(10); - response = given() - .accept("application/vnd.api+json") - .get("/tableExport/" + id); - - String outputResponse = response.jsonPath().getString("data.attributes.status"); - - // If Table Export is completed - if (outputResponse.equals("COMPLETE")) { - break; - } - assertEquals("PROCESSING", outputResponse, "Table Export has failed."); - i++; - assertNotEquals(1000, i, "Table Export not completed."); - } - - return response; - } - - private String getGraphQLResponse(String id) throws InterruptedException { - String responseGraphQL = null; - int i = 0; - while (i < 1000) { - Thread.sleep(10); - responseGraphQL = given() - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.APPLICATION_JSON) - .body("{\"query\":\"{ tableExport(ids: [\\\"" + id + "\\\"]) " - + "{ edges { node { id queryType status result " - + "{ message url httpStatus recordCount } } } } }\"," - + "\"variables\":null}") - .post("/graphQL") - .asString(); - // If Table Export is created and completed - if (responseGraphQL.contains("\"status\":\"COMPLETE\"")) { - break; - } - assertTrue(responseGraphQL.contains("\"status\":\"PROCESSING\""), "TableExport has failed."); - i++; - assertNotEquals(1000, i, "TableExport not completed."); - } - - return responseGraphQL; + public String getGraphQLResponse(String id) throws InterruptedException { + return super.getGraphQLResponse(id, "message url"); } /** @@ -294,14 +157,14 @@ public void jsonApiHappyPath1() throws InterruptedException, IOException { .body("data.attributes.result.message", nullValue()) .body("data.attributes.result.recordCount", equalTo(3)) .body("data.attributes.result.url", - equalTo("http://localhost:" + port + "/export/edc4a871-dff2-4054-804e-d80075cf830a")) + equalTo("http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cf830a")) .body("data.attributes.result.httpStatus", equalTo(200)); assertEquals("\"title\"\n" + "\"For Whom the Bell Tolls\"\n" + "\"Song of Ice and Fire\"\n" - + "\"Ender's Game\"\n", getStoredFileContents(port, "edc4a871-dff2-4054-804e-d80075cf830a")); + + "\"Ender's Game\"\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cf830a")); } /** @@ -341,14 +204,14 @@ public void jsonApiHappyPath2() throws InterruptedException, IOException { .body("data.attributes.result.message", nullValue()) .body("data.attributes.result.recordCount", equalTo(3)) .body("data.attributes.result.url", - equalTo("http://localhost:" + port + "/export/edc4a871-dff2-4054-804e-d80075cf831a")) + equalTo("http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cf831a")) .body("data.attributes.result.httpStatus", equalTo(200)); assertEquals("[\n" + "{\"title\":\"For Whom the Bell Tolls\"}\n" + ",{\"title\":\"Song of Ice and Fire\"}\n" + ",{\"title\":\"Ender's Game\"}\n" - + "]\n", getStoredFileContents(port, "edc4a871-dff2-4054-804e-d80075cf831a")); + + "]\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cf831a")); } @@ -405,14 +268,14 @@ public void graphQLHappyPath1() throws InterruptedException, IOException { String responseGraphQL = getGraphQLResponse("edc4a871-dff2-4054-804e-d80075cf828e"); expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cf828e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," - + "\"result\":{\"message\":null,\"url\":\"http://localhost:" + port + "/export/edc4a871-dff2-4054-804e-d80075cf828e\"," + + "\"result\":{\"message\":null,\"url\":\"http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cf828e\"," + "\"httpStatus\":200,\"recordCount\":3}}}]}}}"; assertEquals(expectedResponse, responseGraphQL); assertEquals("\"title\"\n" + "\"Ender's Game\"\n" + "\"Song of Ice and Fire\"\n" - + "\"For Whom the Bell Tolls\"\n", getStoredFileContents(port, "edc4a871-dff2-4054-804e-d80075cf828e")); + + "\"For Whom the Bell Tolls\"\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cf828e")); } /** @@ -477,7 +340,7 @@ public void graphQLHappyPath2() throws InterruptedException, IOException { .asString(); expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cf829e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," - + "\"result\":{\"url\":\"http://localhost:" + port + "/export/edc4a871-dff2-4054-804e-d80075cf829e\"," + + "\"result\":{\"url\":\"http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cf829e\"," + "\"httpStatus\":200,\"recordCount\":3}}}]}}}"; assertEquals(expectedResponse, responseGraphQL); @@ -485,7 +348,143 @@ public void graphQLHappyPath2() throws InterruptedException, IOException { + "{\"title\":\"Ender's Game\"}\n" + ",{\"title\":\"Song of Ice and Fire\"}\n" + ",{\"title\":\"For Whom the Bell Tolls\"}\n" - + "]\n", getStoredFileContents(port, "edc4a871-dff2-4054-804e-d80075cf829e")); + + "]\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cf829e")); + } + + /** + * Test for a GraphQL query as a Table Export Request with asyncAfterSeconds value set to 0. + * Happy Path Test Scenario 1 with alias. + * @throws InterruptedException InterruptedException + * @throws IOException IOException + */ + @Test + public void graphQLHappyPath1Alias() throws InterruptedException, IOException { + + TableExport queryObj = new TableExport(); + queryObj.setId("edc4a871-dff2-4054-804e-d80075cab28e"); + queryObj.setAsyncAfterSeconds(0); + queryObj.setQueryType("GRAPHQL_V1_0"); + queryObj.setStatus("QUEUED"); + queryObj.setResultType("CSV"); + queryObj.setQuery("{\"query\":\"{ book { edges { node { bookName:title } } } }\",\"variables\":null}"); + String graphQLRequest = document( + mutation( + selection( + field( + "tableExport", + arguments( + argument("op", "UPSERT"), + argument("data", queryObj, UNQUOTED_VALUE) + ), + selections( + field("id"), + field("query"), + field("queryType"), + field("status"), + field("resultType") + ) + ) + ) + ) + ).toQuery(); + + String expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cab28e\"," + + "\"query\":\"{\\\"query\\\":\\\"{ book { edges { node { bookName:title } } } }\\\",\\\"variables\\\":null}\"," + + "\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"PROCESSING\",\"resultType\":\"CSV\"}}]}}}"; + JsonNode graphQLJsonNode = toJsonNode(graphQLRequest, null); + given() + .contentType(MediaType.APPLICATION_JSON) + .header("sleep", "1000") + .accept(MediaType.APPLICATION_JSON) + .body(graphQLJsonNode) + .post("/graphQL") + .then() + .statusCode(org.apache.http.HttpStatus.SC_OK) + .body(equalTo(expectedResponse)); + + String responseGraphQL = getGraphQLResponse("edc4a871-dff2-4054-804e-d80075cab28e"); + expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cab28e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," + + "\"result\":{\"message\":null,\"url\":\"http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cab28e\"," + + "\"httpStatus\":200,\"recordCount\":3}}}]}}}"; + + assertEquals(expectedResponse, responseGraphQL); + assertEquals("\"bookName\"\n" + + "\"Ender's Game\"\n" + + "\"Song of Ice and Fire\"\n" + + "\"For Whom the Bell Tolls\"\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cab28e")); + } + + /** + * Test for a GraphQL query as a TableExport Request with asyncAfterSeconds value set to 7. + * Also uses alias for columns. + * @throws InterruptedException InterruptedException + * @throws IOException IOException + */ + @Test + public void graphQLHappyPath2Alias() throws InterruptedException, IOException { + + TableExport queryObj = new TableExport(); + queryObj.setId("edc4a871-dff2-4054-804e-d80075cab29e"); + queryObj.setAsyncAfterSeconds(7); + queryObj.setQueryType("GRAPHQL_V1_0"); + queryObj.setStatus("QUEUED"); + queryObj.setResultType("JSON"); + queryObj.setQuery("{\"query\":\"{ book { edges { node { bookName:title } } } }\",\"variables\":null}"); + String graphQLRequest = document( + mutation( + selection( + field( + "tableExport", + arguments( + argument("op", "UPSERT"), + argument("data", queryObj, UNQUOTED_VALUE) + ), + selections( + field("id"), + field("query"), + field("queryType"), + field("status"), + field("resultType") + ) + ) + ) + ) + ).toQuery(); + + String expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cab29e\"," + + "\"query\":\"{\\\"query\\\":\\\"{ book { edges { node { bookName:title } } } }\\\",\\\"variables\\\":null}\"," + + "\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\",\"resultType\":\"JSON\"}}]}}}"; + JsonNode graphQLJsonNode = toJsonNode(graphQLRequest, null); + given() + .contentType(MediaType.APPLICATION_JSON) + .header("sleep", "1000") + .accept(MediaType.APPLICATION_JSON) + .body(graphQLJsonNode) + .post("/graphQL") + .then() + .statusCode(org.apache.http.HttpStatus.SC_OK) + .body(equalTo(expectedResponse)); + + String responseGraphQL = given() + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.APPLICATION_JSON) + .body("{\"query\":\"{ tableExport(ids: [\\\"edc4a871-dff2-4054-804e-d80075cab29e\\\"]) " + + "{ edges { node { id queryType status result " + + "{ url httpStatus recordCount } } } } }\"," + + "\"variables\":null}") + .post("/graphQL") + .asString(); + + expectedResponse = "{\"data\":{\"tableExport\":{\"edges\":[{\"node\":{\"id\":\"edc4a871-dff2-4054-804e-d80075cab29e\",\"queryType\":\"GRAPHQL_V1_0\",\"status\":\"COMPLETE\"," + + "\"result\":{\"url\":\"http://localhost:" + getPort() + "/export/edc4a871-dff2-4054-804e-d80075cab29e\"," + + "\"httpStatus\":200,\"recordCount\":3}}}]}}}"; + + assertEquals(expectedResponse, responseGraphQL); + assertEquals("[\n" + + "{\"bookName\":\"Ender's Game\"}\n" + + ",{\"bookName\":\"Song of Ice and Fire\"}\n" + + ",{\"bookName\":\"For Whom the Bell Tolls\"}\n" + + "]\n", getStoredFileContents(getPort(), "edc4a871-dff2-4054-804e-d80075cab29e")); } /** @@ -1115,7 +1114,7 @@ public void noReadEntityTests() throws InterruptedException, IOException { .body("data.attributes.result.httpStatus", equalTo(200)); // Only Header in the file, no records. - String fileContents = getStoredFileContents(port, "0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e"); + String fileContents = getStoredFileContents(getPort(), "0b0dd4e7-9cdc-4bbc-8db2-5c1491c5ee1e"); assertEquals(1, fileContents.split("\n").length); assertTrue(fileContents.contains("field")); @@ -1237,19 +1236,10 @@ public void asyncAfterBeyondMax() throws InterruptedException { } - private JsonNode toJsonNode(String query, Map variables) { - ObjectNode graphqlNode = JsonNodeFactory.instance.objectNode(); - graphqlNode.put("query", query); - if (variables != null) { - graphqlNode.set("variables", mapper.valueToTree(variables)); - } - return graphqlNode; - } - private String getStoredFileContents(Integer port, String id) { return given() .when() - .get("http://localhost:" + port + "/export/" + id) + .get("http://localhost:" + getPort() + "/export/" + id) .then() .statusCode(HttpStatus.SC_OK) .extract().asString(); diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/IntegrationTest.java b/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/IntegrationTest.java index 5c3272d858..609cd8acde 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/IntegrationTest.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/initialization/IntegrationTest.java @@ -120,7 +120,7 @@ protected final Server setUpServer() throws Exception { RestAssured.enableLoggingOfRequestAndResponseIfValidationFails(); // port randomly picked in pom.xml - RestAssured.port = getPort(); + RestAssured.port = getRestAssuredPort(); // embedded jetty server Server server = new Server(RestAssured.port); @@ -178,7 +178,7 @@ protected void assertEqualDocuments(final String actual, final String expected) } } - public static Integer getPort() { + public static Integer getRestAssuredPort() { String restassuredPort = System.getProperty("restassured.port", System.getenv("restassured.port")); return Integer.parseInt(StringUtils.isNotEmpty(restassuredPort) ? restassuredPort : "9999"); }