From f377b81e7017aa6c0a1b708335751408dffbb085 Mon Sep 17 00:00:00 2001 From: Patrick Magee Date: Wed, 26 Jun 2024 10:35:50 -0400 Subject: [PATCH] [CU-86b10kqaj] refactored e2e tests to run faster --- .../com/dnastack/wes/service/BaseE2eTest.java | 47 +- .../com/dnastack/wes/service/WesE2ETest.java | 1236 ++++++++--------- .../wes/service/config/CustomStrategy.java | 45 + .../dnastack/wes/service/util/EnvUtil.java | 40 + .../test/resources/junit-platform.properties | 4 +- 5 files changed, 711 insertions(+), 661 deletions(-) create mode 100644 e2e-tests/src/test/java/com/dnastack/wes/service/config/CustomStrategy.java create mode 100644 e2e-tests/src/test/java/com/dnastack/wes/service/util/EnvUtil.java diff --git a/e2e-tests/src/test/java/com/dnastack/wes/service/BaseE2eTest.java b/e2e-tests/src/test/java/com/dnastack/wes/service/BaseE2eTest.java index 3228576..c4c6c02 100644 --- a/e2e-tests/src/test/java/com/dnastack/wes/service/BaseE2eTest.java +++ b/e2e-tests/src/test/java/com/dnastack/wes/service/BaseE2eTest.java @@ -1,5 +1,6 @@ package com.dnastack.wes.service; +import com.dnastack.wes.service.util.EnvUtil; import io.restassured.RestAssured; import io.restassured.builder.RequestSpecBuilder; import io.restassured.config.EncoderConfig; @@ -25,51 +26,35 @@ public abstract class BaseE2eTest { protected static String scopes; protected static AuthType authType; - enum AuthType { + public enum AuthType { NO_AUTH, OAUTH2, } @BeforeAll public static void setUpAllForAllTests() throws Exception { - tokenUri = optionalEnv("E2E_TOKEN_URI","http://localhost:8081/oauth/token"); - clientId = optionalEnv("E2E_CLIENT_ID", "wes-service-e2e-test"); - clientSecret = optionalEnv("E2E_CLIENT_SECRET", "dev-secret-never-use-in-prod"); - scopes = optionalEnv("E2E_CLIENT_SCOPES", null); - resourceUrl = optionalEnv("E2E_CLIENT_RESOURCE_BASE_URI","http://localhost:8090"); - authType = AuthType.valueOf(optionalEnv("E2E_AUTH_TYPE","OAUTH2")); - - RestAssured.baseURI = optionalEnv("E2E_BASE_URI", "http://localhost:8090"); + tokenUri = EnvUtil.optionalEnv("E2E_TOKEN_URI","http://localhost:8081/oauth/token"); + clientId = EnvUtil.optionalEnv("E2E_CLIENT_ID", "wes-service-e2e-test"); + clientSecret = EnvUtil.optionalEnv("E2E_CLIENT_SECRET", "dev-secret-never-use-in-prod"); + scopes = EnvUtil.optionalEnv("E2E_CLIENT_SCOPES", null); + resourceUrl = EnvUtil.optionalEnv("E2E_CLIENT_RESOURCE_BASE_URI","http://localhost:8090"); + authType = AuthType.valueOf(EnvUtil.optionalEnv("E2E_AUTH_TYPE","OAUTH2")); + + RestAssured.baseURI = EnvUtil.optionalEnv("E2E_BASE_URI", "http://localhost:8090"); RestAssured.config = RestAssuredConfig.config() .encoderConfig(EncoderConfig.encoderConfig().appendDefaultContentCharsetToContentTypeIfUndefined(false)); } - public static String requiredEnv(String name) { - String val = System.getenv(name); - if (val == null) { - fail("Environment variable `" + name + "` is required"); - } - return val; - } - - public static String optionalEnv(String name, String defaultValue) { - String val = System.getenv(name); - if (val == null) { - return defaultValue; - } - return val; - } - - public Header getHeader(String requiredResource) { + public static Header getHeader(String requiredResource) { return getHeader(requiredResource, null); } - public Header getHeader(String requiredResources, Set requiredScopes) { + public static Header getHeader(String requiredResources, Set requiredScopes) { return new Header("Authorization", "Bearer " + getAccessToken(requiredResources, requiredScopes)); } - public RequestSpecification getRequest(){ + public static RequestSpecification getRequest(){ RequestSpecification requestSpecification = given().log().ifValidationFails(); if (authType.equals(AuthType.OAUTH2)) { return requestSpecification.auth().oauth2(getAccessToken(resourceUrl.endsWith("/") ? resourceUrl : resourceUrl + "/" ,null)); @@ -78,15 +63,15 @@ public RequestSpecification getRequest(){ } } - public RequestSpecification getUnauthenticatedRequest(){ + public static RequestSpecification getUnauthenticatedRequest(){ return given().log().ifValidationFails(); } - public RequestSpecification getJsonRequest(){ + public static RequestSpecification getJsonRequest(){ return getRequest().accept(ContentType.JSON); } - private String getAccessToken(String requiredResources, Set requiredScopes) { + private static String getAccessToken(String requiredResources, Set requiredScopes) { Objects.requireNonNull(requiredResources); RequestSpecification specification = new RequestSpecBuilder() diff --git a/e2e-tests/src/test/java/com/dnastack/wes/service/WesE2ETest.java b/e2e-tests/src/test/java/com/dnastack/wes/service/WesE2ETest.java index a0a2510..8ea52b0 100644 --- a/e2e-tests/src/test/java/com/dnastack/wes/service/WesE2ETest.java +++ b/e2e-tests/src/test/java/com/dnastack/wes/service/WesE2ETest.java @@ -1,5 +1,6 @@ package com.dnastack.wes.service; +import com.dnastack.wes.service.util.EnvUtil; import com.dnastack.wes.service.wdl.WdlSupplier; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -9,8 +10,10 @@ import io.restassured.specification.MultiPartSpecification; import org.awaitility.Awaitility; import org.awaitility.core.ConditionFactory; -import org.junit.jupiter.api.*; -import org.junit.jupiter.api.TestInstance.Lifecycle; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -27,38 +30,108 @@ @DisplayName("WES tests") -public class WesE2ETest extends BaseE2eTest { +class WesE2ETest extends BaseE2eTest { - static final Duration maxWait = Duration.parse(optionalEnv("E2E_MAX_WORKFLOW_WAIT_TIME","PT10M")); + static final Duration maxWait = Duration.parse(EnvUtil.optionalEnv("E2E_MAX_WORKFLOW_WAIT_TIME", "PT10M")); static final List TERMINAL_STATES = List.of("COMPLETE", "EXECUTOR_ERROR", "SYSTEM_ERROR", "CANCELED"); - ConditionFactory pollInterval = with() + static final ConditionFactory pollInterval = with() .ignoreException(AssertionError.class) .pollDelay(Duration.ofSeconds(2)).and() .pollInterval(Duration.ofSeconds(3)) .atMost(maxWait); private static final WdlSupplier supplier = new WdlSupplier(); + + static Stream completeWorkflowWithFilesProvider() throws Exception { + Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + + + String workflowJobIdWithAllOutputTypes = getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_ALL_OUTPUT_TYPES).getBytes())) + .multiPart(getJsonMultipart("workflow_params", inputs)) + .post("/ga4gh/wes/v1/runs") + .then() + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())) + .extract() + .jsonPath() + .getString("run_id"); + + + pollUntilJobCompletes(workflowJobIdWithAllOutputTypes); + return Stream.of(Arguments.of(workflowJobIdWithAllOutputTypes)); + } + + + private static String workflowJobId; + private static final Object LOCK = new Object(); + + + static private Stream submittedWorkflowWithoutFiles() { + + + return Stream.of(Arguments.of(submitWorkflowAndGetId())); + } + + static private Stream completeSubmittedWorkflowWithoutFiles() throws Exception { + String workflowJobId = submitWorkflowAndGetId(); + pollUntilJobCompletes(workflowJobId); + return Stream.of(Arguments.of(workflowJobId)); + } + + private static String submitWorkflowAndGetId() { + synchronized (LOCK) { + if (workflowJobId == null) { + Map tags = Collections.singletonMap("WES", "TestRun"); + Map engineParams = Collections.singletonMap("write_to_cache", false); + Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + + + workflowJobId = getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) + .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) + .multiPart(getJsonMultipart("tags", tags)) + .multiPart(getJsonMultipart("workflow_params", inputs)) + .post("/ga4gh/wes/v1/runs") + .then() + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())) + .extract() + .jsonPath() + .getString("run_id"); + + with().pollDelay(Duration.ofSeconds(15)).timeout(Duration.ofSeconds(30)).until(() -> true); + } + } + return workflowJobId; + } + + @Test @DisplayName("Can retrieve the service information when unauthorized and the summary is empty") public void canGetServiceInfoNoAuth() { Assumptions.assumeFalse(authType.equals(AuthType.NO_AUTH)); - //@formatter:off + given() .log().uri() .log().method() .accept(ContentType.JSON) - .get("/ga4gh/wes/v1/service-info") - .then() + .get("/ga4gh/wes/v1/service-info") + .then() .assertThat() .statusCode(200) - .body("workflow_type_versions",hasKey("WDL")) - .body("workflow_type_versions.WDL",allOf(hasItem("draft-2"),hasItem("1.0"))) - .body("supported_wes_versions",hasItem("1.0.0")) - .body("supported_filesystem_protocols",anyOf(hasItem("file"),hasItem("gs"),hasItem("http"),hasItem("drs"))) + .body("workflow_type_versions", hasKey("WDL")) + .body("workflow_type_versions.WDL", allOf(hasItem("draft-2"), hasItem("1.0"))) + .body("supported_wes_versions", hasItem("1.0.0")) + .body("supported_filesystem_protocols", anyOf(hasItem("file"), hasItem("gs"), hasItem("http"), hasItem("drs"))) .body("$", not(hasKey("system_state_counts"))) - .body("auth_instruction_url",not(isEmptyOrNullString())); - //@formatter:on + .body("auth_instruction_url", not(isEmptyOrNullString())); + } @@ -86,13 +159,13 @@ private String getResource(String path) { @DisplayName("Listing all runs unauthorized returns 401 response") public void listingRunsUnauthorizedError() { Assumptions.assumeFalse(authType.equals(AuthType.NO_AUTH)); - //@formatter:off + getUnauthenticatedRequest() - .get("/ga4gh/wes/v1/runs") - .then() + .get("/ga4gh/wes/v1/runs") + .then() .assertThat() .statusCode(401); - //@formatter:on + } @@ -115,737 +188,644 @@ public void listingRunsIncorrectResourceError() { @Test @DisplayName("Listing all runs returns response with") public void listingRunsReturnsEmptyResponse() { - //@formatter:off + getJsonRequest() - .queryParam("page_size",5) - .get("/ga4gh/wes/v1/runs") - .then() + .queryParam("page_size", 5) + .get("/ga4gh/wes/v1/runs") + .then() .assertThat() .statusCode(200) - .body("runs.size()",lessThanOrEqualTo(5)); - //@formatter:on + .body("runs.size()", lessThanOrEqualTo(5)); - } - private void pollUntilJobCompletes(String workflowJobId) throws Exception { - waitForRunTerminalState(workflowJobId, "COMPLETE"); } - void waitForRunTerminalState(String workflowJobId, String expectedState) { - String actualState = pollInterval.until(() -> - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/status", workflowJobId) - .then() - .statusCode(200) - .body("state", in(TERMINAL_STATES)) - .extract() - .jsonPath().getString("state") - , TERMINAL_STATES::contains); - - Assertions.assertEquals(expectedState, actualState, - "Expecting run %s to have a terminal state of '%s', however the state is '%s'".formatted(workflowJobId, expectedState, actualState)); + @Test + @DisplayName("Workflow Run Submission with valid payload should succeed") + public void submitValidWorkflowRun() { + Map tags = Collections.singletonMap("WES", "TestRun"); + Map engineParams = new HashMap<>(); + Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + + + getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) + .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) + .multiPart(getJsonMultipart("tags", tags)) + .multiPart(getJsonMultipart("workflow_params", inputs)) + .post("/ga4gh/wes/v1/runs") + .then() + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())); } - private MultiPartSpecification getWorkflowUrlMultipart(String inputString) { - return new MultiPartSpecBuilder(inputString) - .controlName("workflow_url") - .mimeType(ContentType.TEXT.toString()) - .charset(StandardCharsets.UTF_8) - .emptyFileName() - .build(); - } - private MultiPartSpecification getJsonMultipart(String controlName, Map content) { - return new MultiPartSpecBuilder(content) - .controlName(controlName) - .mimeType(ContentType.JSON.toString()) - .charset(StandardCharsets.UTF_8) - .emptyFileName() - .build(); - } + @Test + @DisplayName("Workflow Run Submission with invalid url and unsupported attachment should fail") + public void submitWorkflowRunWithInvalidPayloadShouldFail() { - private MultiPartSpecification getMultipartAttachment(String fileName, Map content) { - return new MultiPartSpecBuilder(content) - .controlName("workflow_attachment") - .fileName(fileName) - .mimeType(ContentType.JSON.toString()) - .charset(StandardCharsets.UTF_8) - .emptyFileName() - .build(); - } + getRequest() + .multiPart("workflow_attachment", "echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes()) + .post("/ga4gh/wes/v1/runs") + .then() + .assertThat() + .statusCode(400); - private MultiPartSpecification getMultipartAttachment(String fileName, byte[] content) { - return new MultiPartSpecBuilder(content) - .controlName("workflow_attachment") - .mimeType(ContentType.BINARY.toString()) - .charset(StandardCharsets.UTF_8) - .fileName(fileName) - .build(); } - @DisplayName("Test Workflow Run Submissions") - @Nested - public class WorkflowRunSubmissions { - - @Test - @DisplayName("Workflow Run Submission with valid payload should succeed") - public void submitValidWorkflowRun() { - Map tags = Collections.singletonMap("WES", "TestRun"); - Map engineParams = new HashMap<>(); - Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); - - //@formatter:off - getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) - .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) - .multiPart(getJsonMultipart("tags", tags)) - .multiPart(getJsonMultipart("workflow_params", inputs)) + @Test + @DisplayName("Workflow Run Submission with valid multiple attachments should succeed") + public void submitValidWorkflowRunWithMultipleAttachments() throws IOException { + ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeReference = new TypeReference>() { + }; + Map inputs = mapper + .readValue(supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_INPUTS), typeReference); + + getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_1).getBytes())) + .multiPart(getMultipartAttachment(WdlSupplier.WORKFLOW_WITH_IMPORTS_2, supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_2).getBytes())) + .multiPart(getJsonMultipart("workflow_params", inputs)) .post("/ga4gh/wes/v1/runs") .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())); - //@formatter:on - } + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())); + } - @Test - @DisplayName("Workflow Run Submission with invalid url and unsupported attachment should fail") - public void submitWorkflowRunWithInvalidPayloadShouldFail() { - //@formatter:off - getRequest() - .multiPart("workflow_attachment","echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes()) + @Test + @DisplayName("Workflow Run Submission with valid multiple attachments should succeed") + public void submitValidWorkflowRunWithSubWorkflowFlattensTasks() throws Exception { + ObjectMapper mapper = new ObjectMapper(); + TypeReference> typeReference = new TypeReference>() { + }; + Map inputs = mapper + .readValue(supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_INPUTS), typeReference); + + String workflowJobId = getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_1).getBytes())) + .multiPart(getMultipartAttachment(WdlSupplier.WORKFLOW_WITH_IMPORTS_2, supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_2).getBytes())) + .multiPart(getJsonMultipart("workflow_params", inputs)) .post("/ga4gh/wes/v1/runs") .then() - .assertThat() - .statusCode(400); - //@formatter:on - } + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())) + .extract() - @Test - @DisplayName("Workflow Run Submission with valid multiple attachments should succeed") - public void submitValidWorkflowRunWithMultipleAttachments() throws IOException { - ObjectMapper mapper = new ObjectMapper(); - TypeReference> typeReference = new TypeReference>() { - }; - Map inputs = mapper - .readValue(supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_INPUTS), typeReference); - //@formatter:off - getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_1).getBytes())) - .multiPart(getMultipartAttachment(WdlSupplier.WORKFLOW_WITH_IMPORTS_2,supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_2).getBytes())) - .multiPart(getJsonMultipart("workflow_params", inputs)) - .post("/ga4gh/wes/v1/runs") - .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())); - //@formatter:on - } + .jsonPath() + .getString("run_id"); - @Test - @DisplayName("Workflow Run Submission with valid multiple attachments should succeed") - public void submitValidWorkflowRunWithSubWorkflowFlattensTasks() throws Exception { - ObjectMapper mapper = new ObjectMapper(); - TypeReference> typeReference = new TypeReference>() { - }; - Map inputs = mapper - .readValue(supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_INPUTS), typeReference); - //@formatter:off - String workflowJobId = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_1).getBytes())) - .multiPart(getMultipartAttachment(WdlSupplier.WORKFLOW_WITH_IMPORTS_2,supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_IMPORTS_2).getBytes())) - .multiPart(getJsonMultipart("workflow_params", inputs)) - .post("/ga4gh/wes/v1/runs") - .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())) - .extract() + pollUntilJobCompletes(workflowJobId); - .jsonPath() - .getString("run_id"); - //@formatter:on - pollUntilJobCompletes(workflowJobId); - - final String runLogPath = "/ga4gh/wes/v1/runs/" + workflowJobId; - getRequest() - .get(runLogPath) - .then() - .assertThat() - .body("task_logs", hasSize(equalTo(4))); - //@formatter:on - } + final String runLogPath = "/ga4gh/wes/v1/runs/" + workflowJobId; + getRequest() + .get(runLogPath) + .then() + .assertThat() + .body("task_logs", hasSize(equalTo(4))); - @Test - @DisplayName("Workflow Run Submission with valid payload and options should succeed") - public void submitValidWorkflowRunWithOptionsAttachment() throws InterruptedException { - Map tags = Collections.singletonMap("WES", "TestRun"); - Map engineParams = Collections.singletonMap("write_to_cache", false); - Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); - - //@formatter:off - String runId = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) - .multiPart(getMultipartAttachment( "options.json",engineParams)) - .multiPart(getJsonMultipart("tags", tags)) - .multiPart(getJsonMultipart("workflow_params", inputs)) + } + + @Test + @DisplayName("Workflow Run Submission with valid payload and options should succeed") + public void submitValidWorkflowRunWithOptionsAttachment() throws InterruptedException { + Map tags = Collections.singletonMap("WES", "TestRun"); + Map engineParams = Collections.singletonMap("write_to_cache", false); + Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + + + String runId = getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) + .multiPart(getMultipartAttachment("options.json", engineParams)) + .multiPart(getJsonMultipart("tags", tags)) + .multiPart(getJsonMultipart("workflow_params", inputs)) .post("/ga4gh/wes/v1/runs") .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())) - .extract() - .jsonPath() - .getString("run_id"); - //@formatter:on + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())) + .extract() + .jsonPath() + .getString("run_id"); - } - @Test - @DisplayName("Uploading attachment file can be used as workflow input") - public void uploadWorkflowAttachmentWithRunSubmission() throws Exception { - Map tags = Collections.singletonMap("WES", "TestRun"); - Map engineParams = Collections.singletonMap("write_to_cache", false); - Map inputs = Collections.singletonMap("test.input_file", "name.txt"); - - //@formatter:off - String runId = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.CAT_FILE_WORKFLOW).getBytes())) - .multiPart(getMultipartAttachment("name.txt","Frank".getBytes())) - .multiPart(getJsonMultipart("workflow_engine_parameters",engineParams)) - .multiPart(getJsonMultipart("tags",tags)) - .multiPart(getJsonMultipart("workflow_params",inputs)) + } + + @Test + @DisplayName("Uploading attachment file can be used as workflow input") + public void uploadWorkflowAttachmentWithRunSubmission() throws Exception { + Map tags = Collections.singletonMap("WES", "TestRun"); + Map engineParams = Collections.singletonMap("write_to_cache", false); + Map inputs = Collections.singletonMap("test.input_file", "name.txt"); + + + String runId = getRequest() + .multiPart(getWorkflowUrlMultipart("echo.wdl")) + .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.CAT_FILE_WORKFLOW).getBytes())) + .multiPart(getMultipartAttachment("name.txt", "Frank".getBytes())) + .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) + .multiPart(getJsonMultipart("tags", tags)) + .multiPart(getJsonMultipart("workflow_params", inputs)) .post("/ga4gh/wes/v1/runs") .then() - .log().everything() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())) - .extract() - .jsonPath() - .getString("run_id"); - //@formatter:on - pollUntilJobCompletes(runId); - - //@formatter:off - getJsonRequest() + .log().everything() + .assertThat() + .statusCode(200) + .body("run_id", is(notNullValue())) + .extract() + .jsonPath() + .getString("run_id"); + + pollUntilJobCompletes(runId); + + + getJsonRequest() .get("/ga4gh/wes/v1/runs/{runId}", runId) .then() .assertThat() - .statusCode(200) - .body("run_id",equalTo(runId)) - .body("state",equalTo("COMPLETE")) - .body("run_log",not(isEmptyOrNullString())) - .body("outputs[\"test.o\"]",equalTo("Frank")); - } + .statusCode(200) + .body("run_id", equalTo(runId)) + .body("state", equalTo("COMPLETE")) + .body("run_log", not(isEmptyOrNullString())) + .body("outputs[\"test.o\"]", equalTo("Frank")); + } - @Nested - @DisplayName("Test run methods where Workflows have previously been submitted") - @TestInstance(Lifecycle.PER_CLASS) - public class RunMethodsWithExistingJobs { + @ParameterizedTest + @MethodSource("submittedWorkflowWithoutFiles") + @DisplayName("Get Run Log Shows extended information appropriately") + public void getRunLogReturnsAccurateData(String workflowJobId) { - String workflowJobId; - @BeforeAll - public void setup() throws InterruptedException { - Map tags = Collections.singletonMap("WES", "TestRun"); - Map engineParams = Collections.singletonMap("write_to_cache", false); - Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}", workflowJobId) + .then() + .assertThat() + .statusCode(200) + .body("run_id", equalTo(workflowJobId)) + .body("request.workflow_engine_parameters.write_to_cache", equalTo("false")) + .body("request.tags.WES", equalTo("TestRun")) + .body("state", notNullValue()) + .body("run_log", not(isEmptyOrNullString())); - //@formatter:off - workflowJobId = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) - .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) - .multiPart(getJsonMultipart("tags", tags)) - .multiPart(getJsonMultipart("workflow_params", inputs)) - .post("/ga4gh/wes/v1/runs") - .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())) - .extract() - .jsonPath() - .getString("run_id"); - //@formatter:on - Thread.sleep(15000L); - } + } + @ParameterizedTest + @MethodSource("submittedWorkflowWithoutFiles") + @DisplayName("Get Run Status for existing run returns current state") + public void getRunStatusReturnsJobStatus(String workflowJobId) { - @Test - @DisplayName("Get Run Log Shows extended information appropriately") - public void getRunLogReturnsAccurateData() { - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}", workflowJobId) - .then() - .assertThat() - .statusCode(200) - .body("run_id",equalTo(workflowJobId)) - .body("request.workflow_engine_parameters.write_to_cache",equalTo("false")) - .body("request.tags.WES",equalTo("TestRun")) - .body("state",notNullValue()) - .body("run_log",not(isEmptyOrNullString())); - //@formatter:on + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/status", workflowJobId) + .then() + .assertThat() + .statusCode(200) + .body("run_id", equalTo(workflowJobId)) + .body("state", notNullValue()); - } + } - @Test - @DisplayName("Get Run Status for existing run returns current state") - public void getRunStatusReturnsJobStatus() { - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/status", workflowJobId ) - .then() - .assertThat() - .statusCode(200) - .body("run_id",equalTo(workflowJobId)) - .body("state",notNullValue()); - //@formatter:on - } + @Test + @DisplayName("Get Run Status for non-existent run fails with status 401 or 404") + public void getRunStatusForNonExistentRunShouldFail() { - @Test - @DisplayName("Get Run Status for non-existent run fails with status 401 or 404") - public void getRunStatusForNonExistentRunShouldFail() { + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/status", UUID.randomUUID()) + .then() + .assertThat() + .statusCode(anyOf(equalTo(404), equalTo(401))); - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/status", UUID.randomUUID() ) - .then() - .assertThat() - .statusCode(anyOf(equalTo(404),equalTo(401))); - //@formatter:on - } + } - @Test - @DisplayName("Get Run Log for non-existent run fails with status 401 or 404") - public void getRunLogForNonExistentRunShouldFail() { + @Test + @DisplayName("Get Run Log for non-existent run fails with status 401 or 404") + public void getRunLogForNonExistentRunShouldFail() { - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/" + UUID.randomUUID()) - .then() - .assertThat() - .statusCode(anyOf(equalTo(404),equalTo(401))); - //@formatter:on - } + getJsonRequest() + .get("/ga4gh/wes/v1/runs/" + UUID.randomUUID()) + .then() + .assertThat() + .statusCode(anyOf(equalTo(404), equalTo(401))); - @Test - @DisplayName("List Runs includes current job") - public void listRunsReturnsReturnsNonEmptyCollection() { + } - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs") - .then() - .assertThat() - .statusCode(200) - .body("runs.size()",greaterThan(0)) - .body("runs.findAll { it.run_id == /" + workflowJobId +"/ }",notNullValue()); - //@formatter:on - } + @ParameterizedTest + @MethodSource("submittedWorkflowWithoutFiles") + @DisplayName("List Runs includes current job") + public void listRunsReturnsReturnsNonEmptyCollection(String workflowJobId) { - @ParameterizedTest - @MethodSource("completeWorkflowWithFilesProvider") - @DisplayName("Get Run Files for existing run returns all files") - public void getRunFilesReturnsNonEmptyCollection(String runId) { - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/files", runId) - .then() - .assertThat() - .statusCode(200) - .body("runFiles.size()", greaterThan(0)) - .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)); - //@formatter:on - } + getJsonRequest() + .get("/ga4gh/wes/v1/runs") + .then() + .assertThat() + .statusCode(200) + .body("runs.size()", greaterThan(0)) + .body("runs.findAll { it.run_id == /" + workflowJobId + "/ }", notNullValue()); - @ParameterizedTest - @MethodSource("completeWorkflowWithFilesProvider") - @DisplayName("Get Run File Metadata for existing run returns metadata") - public void getFileMetadataForExistingRun(String runId) { + } - //@formatter:off - String filePath = getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/files", runId) - .then() - .assertThat() - .statusCode(200) - .body("runFiles.size()", greaterThan(0)) - .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)) - .extract() - .jsonPath().getString("runFiles[0].path"); - getJsonRequest() - .queryParam("path",filePath) - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .statusCode(200) - .body("path",equalTo(filePath)) - .body("size",notNullValue()) - .body("name",notNullValue()); + @ParameterizedTest + @MethodSource("completeWorkflowWithFilesProvider") + @DisplayName("Get Run Files for existing run returns all files") + public void getRunFilesReturnsNonEmptyCollection(String runId) { - // Test Json Content - getJsonRequest() - .queryParam("path","$.outputs['hello_world.out']") - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .statusCode(200) - .body("path",equalTo(filePath)) - .body("size",notNullValue()) - .body("name",notNullValue()); - getJsonRequest() - .queryParam("path","/foo/bar/biz/baz") - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .statusCode(404); + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/files", runId) + .then() + .assertThat() + .statusCode(200) + .body("runFiles.size()", greaterThan(0)) + .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)); - getJsonRequest() - .queryParam("path","$.inputs['hello_world.name']") - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .statusCode(404); - //@formatter:on - } + } - @ParameterizedTest - @MethodSource("completeWorkflowWithFilesProvider") - @DisplayName("Stream Run Files for existing run streams contents") - public void streamFileBytesForExistingRun(String runId) { + @ParameterizedTest + @MethodSource("completeWorkflowWithFilesProvider") + @DisplayName("Get Run File Metadata for existing run returns metadata") + public void getFileMetadataForExistingRun(String runId) { - //@formatter:off - String filePath = getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/files", runId ) - .then() - .assertThat() - .statusCode(200) - .body("runFiles.size()", greaterThan(0)) - .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)) - .extract() - .jsonPath().getString("runFiles[0].path"); - getJsonRequest() - .accept(ContentType.BINARY) - .queryParam("path",filePath) - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .contentType(ContentType.BINARY) - .header("Last-Modified",notNullValue()) - .header("Content-Length",notNullValue()) - .statusCode(200); + String filePath = getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/files", runId) + .then() + .assertThat() + .statusCode(200) + .body("runFiles.size()", greaterThan(0)) + .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)) + .extract() + .jsonPath().getString("runFiles[0].path"); - // Test Json Content - getJsonRequest() - .accept(ContentType.BINARY) - .queryParam("path","$.outputs['hello_world.out']") - .get("/ga4gh/wes/v1/runs/{runId}/file",runId) - .then() - .assertThat() - .statusCode(200) - .contentType(ContentType.BINARY) - .header("Last-Modified",notNullValue()) - .header("Content-Length",notNullValue()); - //@formatter:on - } + getJsonRequest() + .queryParam("path", filePath) + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .statusCode(200) + .body("path", equalTo(filePath)) + .body("size", notNullValue()) + .body("name", notNullValue()); + // Test Json Content + getJsonRequest() + .queryParam("path", "$.outputs['hello_world.out']") + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .statusCode(200) + .body("path", equalTo(filePath)) + .body("size", notNullValue()) + .body("name", notNullValue()); - @Test - @DisplayName("Get Run Files for non-existent run fails with status 401 or 404") - public void getRunFilesForNonExistentRunShouldFail() { + getJsonRequest() + .queryParam("path", "/foo/bar/biz/baz") + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .statusCode(404); - //@formatter:off - getJsonRequest() - .get("/ga4gh/wes/v1/runs/{runId}/files", UUID.randomUUID()) - .then() - .assertThat() - .statusCode(anyOf(equalTo(404), equalTo(401))); - //@formatter:on - } + getJsonRequest() + .queryParam("path", "$.inputs['hello_world.name']") + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .statusCode(404); + } - @ParameterizedTest - @MethodSource("completeWorkflowWithFilesProvider") - @DisplayName("Delete Run Files for existing run returns all deleted files") - public void deleteRunFilesReturnsNonEmptyCollection(String runId) { - //@formatter:off - getJsonRequest() - .delete("/ga4gh/wes/v1/runs/{runId}/files", runId ) - .then() - .assertThat() - .statusCode(200) - .body("deletions.size()", greaterThan(0)) - .body("deletions.every { it.path != null && it.file_type == 'SECONDARY' && (it.state == 'DELETED' || it.state == 'NOT_FOUND') }", equalTo(true)); - //@formatter:on - } + @ParameterizedTest + @MethodSource("completeWorkflowWithFilesProvider") + @DisplayName("Stream Run Files for existing run streams contents") + public void streamFileBytesForExistingRun(String runId) { - @ParameterizedTest - @MethodSource("completeWorkflowWithFilesProvider") - @DisplayName("Delete Run Files for existing run asynchronously returns all deleted files") - public void deleteRunFilesAsyncReturnsNonEmptyCollection(String runId) { - JsonPath path = getJsonRequest() - .queryParam("async", true) - .delete("/ga4gh/wes/v1/runs/{runId}/files", runId) - .then() - .assertThat() - .statusCode(200) - .body("deletions.size()", greaterThan(0)) - .body("deletions.every { it.path != null && it.file_type == 'SECONDARY' && it.state == 'ASYNC' }", equalTo(true)) - .extract() - .jsonPath(); + String filePath = getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/files", runId) + .then() + .assertThat() + .statusCode(200) + .body("runFiles.size()", greaterThan(0)) + .body("runFiles.every { it.path != null && it.file_type in ['FINAL', 'SECONDARY', 'LOG'] }", equalTo(true)) + .extract() + .jsonPath().getString("runFiles[0].path"); - String deletedPath = path.getString("deletions[0].path"); + getJsonRequest() + .accept(ContentType.BINARY) + .queryParam("path", filePath) + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .contentType(ContentType.BINARY) + .header("Last-Modified", notNullValue()) + .header("Content-Length", notNullValue()) + .statusCode(200); + // Test Json Content + getJsonRequest() + .accept(ContentType.BINARY) + .queryParam("path", "$.outputs['hello_world.out']") + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat() + .statusCode(200) + .contentType(ContentType.BINARY) + .header("Last-Modified", notNullValue()) + .header("Content-Length", notNullValue()); - Awaitility.await() - .atMost(Duration.ofSeconds(30)) - .pollInterval(Duration.ofSeconds(5)) - .untilAsserted(() -> - getJsonRequest() - .queryParam("path", deletedPath) - .get("/ga4gh/wes/v1/runs/{runId}/file", runId) - .then() - .assertThat().statusCode(404)); - } + } - @Test - @DisplayName("Delete Run Files for non-existent run fails with status 401 or 404") - public void deleteRunFilesForNonExistentRunShouldFail() { + @Test + @DisplayName("Get Run Files for non-existent run fails with status 401 or 404") + public void getRunFilesForNonExistentRunShouldFail() { - //@formatter:off - getJsonRequest() - .delete("/ga4gh/wes/v1/runs/{runId}/files",UUID.randomUUID()) - .then() - .assertThat() - .statusCode(anyOf(equalTo(404),equalTo(401))); - //@formatter:on - } + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/files", UUID.randomUUID()) + .then() + .assertThat() + .statusCode(anyOf(equalTo(404), equalTo(401))); - Stream completeWorkflowWithFilesProvider() throws Exception { - Map inputs = Collections.singletonMap("hello_world.name", "Some sort of String"); + } - //@formatter:off - String workflowJobIdWithAllOutputTypes = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl", supplier.getFileContent(WdlSupplier.WORKFLOW_WITH_ALL_OUTPUT_TYPES).getBytes())) - .multiPart(getJsonMultipart("workflow_params", inputs)) - .post("/ga4gh/wes/v1/runs") - .then() - .assertThat() - .statusCode(200) - .body("run_id", is(notNullValue())) - .extract() - .jsonPath() - .getString("run_id"); - //@formatter:on - pollUntilJobCompletes(workflowJobIdWithAllOutputTypes); - return Stream.of(Arguments.of(workflowJobIdWithAllOutputTypes)); - } + @ParameterizedTest + @MethodSource("completeWorkflowWithFilesProvider") + @DisplayName("Delete Run Files for existing run returns all deleted files") + public void deleteRunFilesReturnsNonEmptyCollection(String runId) { - } + getJsonRequest() + .delete("/ga4gh/wes/v1/runs/{runId}/files", runId) + .then() + .assertThat() + .statusCode(200) + .body("deletions.size()", greaterThan(0)) + .body("deletions.every { it.path != null && it.file_type == 'SECONDARY' && (it.state == 'DELETED' || it.state == 'NOT_FOUND') }", + equalTo(true)); + + } + + @ParameterizedTest + @MethodSource("completeWorkflowWithFilesProvider") + @DisplayName("Delete Run Files for existing run asynchronously returns all deleted files") + public void deleteRunFilesAsyncReturnsNonEmptyCollection(String runId) { + JsonPath path = getJsonRequest() + .queryParam("async", true) + .delete("/ga4gh/wes/v1/runs/{runId}/files", runId) + .then() + .assertThat() + .statusCode(200) + .body("deletions.size()", greaterThan(0)) + .body("deletions.every { it.path != null && it.file_type == 'SECONDARY' && it.state == 'ASYNC' }", equalTo(true)) + .extract() + .jsonPath(); + + String deletedPath = path.getString("deletions[0].path"); + + + Awaitility.await() + .atMost(Duration.ofSeconds(30)) + .pollInterval(Duration.ofSeconds(5)) + .untilAsserted(() -> + getJsonRequest() + .queryParam("path", deletedPath) + .get("/ga4gh/wes/v1/runs/{runId}/file", runId) + .then() + .assertThat().statusCode(404)); } - @DisplayName("Test Workflow Log Access") - @Nested - @TestInstance(Lifecycle.PER_CLASS) - public class WorkflowLogAccess { - String workflowJobId; + @Test + @DisplayName("Delete Run Files for non-existent run fails with status 401 or 404") + public void deleteRunFilesForNonExistentRunShouldFail() { - @BeforeAll - public void setup() throws Exception { - Map tags = Collections.singletonMap("WES", "TestRun"); - Map engineParams = Collections.singletonMap("write_to_cache", false); - Map inputs = Collections.singletonMap("hello_world.name", "Frank"); - //@formatter:off - workflowJobId = getRequest() - .multiPart(getWorkflowUrlMultipart("echo.wdl")) - .multiPart(getMultipartAttachment("echo.wdl",supplier.getFileContent(WdlSupplier.WORKFLOW_WITHOUT_FILE).getBytes())) - .multiPart(getJsonMultipart("workflow_engine_parameters", engineParams)) - .multiPart(getJsonMultipart("tags", tags)) - .multiPart(getJsonMultipart("workflow_params", inputs)) - .post("/ga4gh/wes/v1/runs") + getJsonRequest() + .delete("/ga4gh/wes/v1/runs/{runId}/files", UUID.randomUUID()) .then() - .assertThat() - .statusCode(200) - .body("run_id",is(notNullValue())) - .extract() - .jsonPath() - .getString("run_id"); - //@formatter:on - pollUntilJobCompletes(workflowJobId); - } + .assertThat() + .statusCode(anyOf(equalTo(404), equalTo(401))); + + } + - @Test - @DisplayName("Get Stdout and Stderr for task") - public void getStdoutForTaskReturnsSuccessfully() { - //@formatter:off - Map taskLogs = getJsonRequest() + @ParameterizedTest + @MethodSource("completeSubmittedWorkflowWithoutFiles") + @DisplayName("Get Stdout and Stderr for task") + public void getStdoutForTaskReturnsSuccessfully(String workflowJobId) { + + Map taskLogs = getJsonRequest() .get("/ga4gh/wes/v1/runs/{runId}", workflowJobId) .then() - .assertThat() - .statusCode(200) - .body("run_id",equalTo(workflowJobId)) - .body("state",equalTo("COMPLETE")) - .extract() - .jsonPath() - .getMap("task_logs[0]",String.class,String.class); - //@formatter:on - - Assertions.assertNotNull(taskLogs.get("stderr")); - Assertions.assertNotNull(taskLogs.get("stdout")); - Assertions - .assertTrue(taskLogs.get("stderr").endsWith("/ga4gh/wes/v1/runs/" + workflowJobId + "/logs/task/" + taskLogs.get("id") + "/stderr")); - Assertions - .assertTrue(taskLogs.get("stdout").endsWith("/ga4gh/wes/v1/runs/" + workflowJobId + "/logs/task/" + taskLogs.get("id") + "/stdout")); - - //@formatter:off - String body = getRequest() + .assertThat() + .statusCode(200) + .body("run_id", equalTo(workflowJobId)) + .body("state", equalTo("COMPLETE")) + .extract() + .jsonPath() + .getMap("task_logs[0]", String.class, String.class); + + + Assertions.assertNotNull(taskLogs.get("stderr")); + Assertions.assertNotNull(taskLogs.get("stdout")); + Assertions + .assertTrue(taskLogs.get("stderr").endsWith("/ga4gh/wes/v1/runs/" + workflowJobId + "/logs/task/" + taskLogs.get("id") + "/stderr")); + Assertions + .assertTrue(taskLogs.get("stdout").endsWith("/ga4gh/wes/v1/runs/" + workflowJobId + "/logs/task/" + taskLogs.get("id") + "/stdout")); + + + String body = getRequest() .get(taskLogs.get("stdout")) .then() - .statusCode(200) - .extract().asString(); + .statusCode(200) + .extract().asString(); - Assertions.assertEquals("Hello Frank\n",body); + Assertions.assertEquals("Hello Some sort of String\n", body); - // test range offset - body = getRequest() - .header("Range","bytes=0-4") - .get(taskLogs.get("stdout")) - .then() - .statusCode(206) - .extract().asString(); + // test range offset + body = getRequest() + .header("Range", "bytes=0-4") + .get(taskLogs.get("stdout")) + .then() + .statusCode(206) + .extract().asString(); - Assertions.assertEquals("Hello",body); + Assertions.assertEquals("Hello", body); - body = getRequest() - .header("Range","bytes=6-") - .get(taskLogs.get("stdout")) - .then() - .statusCode(206) - .extract().asString(); + body = getRequest() + .header("Range", "bytes=6-") + .get(taskLogs.get("stdout")) + .then() + .statusCode(206) + .extract().asString(); - Assertions.assertEquals("Frank\n",body); + Assertions.assertEquals("Some sort of String\n", body); - body = getRequest() - .header("Range","bytes=1-3") - .get(taskLogs.get("stdout")) - .then() - .statusCode(206) - .extract().asString(); + body = getRequest() + .header("Range", "bytes=1-3") + .get(taskLogs.get("stdout")) + .then() + .statusCode(206) + .extract().asString(); - Assertions.assertEquals("ell",body); + Assertions.assertEquals("ell", body); - //@formatter:on - //@formatter:off - body = getRequest() + body = getRequest() .get(taskLogs.get("stderr")) .then() - .statusCode(200) - .extract().asString(); - - Assertions.assertEquals("Goodbye Frank\n",body); - //@formatter:on - - // test range offset - body = getRequest() - .header("Range", "bytes=0-4") - .get(taskLogs.get("stderr")) - .then() - .statusCode(206) - .extract().asString(); - - Assertions.assertEquals("Goodb", body); - - body = getRequest() - .header("Range", "bytes=8-") - .get(taskLogs.get("stderr")) - .then() - .statusCode(206) - .extract().asString(); - - Assertions.assertEquals("Frank\n", body); - - body = getRequest() - .header("Range", "bytes=1-3") - .get(taskLogs.get("stderr")) - .then() - .statusCode(206) - .extract().asString(); - - Assertions.assertEquals("ood", body); - - getRequest() - .header("Range", "bytes=1-3,6-9") - .get(taskLogs.get("stderr")) - .then() - .statusCode(416); - } + .statusCode(200) + .extract().asString(); + + Assertions.assertEquals("Goodbye Some sort of String\n", body); - @Test - @DisplayName("Get Stdout and Stderr for task") - public void unauthenticatedUserCannotAccessLogs() { - Assumptions.assumeFalse(authType.equals(AuthType.NO_AUTH)); - //@formatter:off - Map taskLogs = getJsonRequest() + // test range offset + body = getRequest() + .header("Range", "bytes=0-4") + .get(taskLogs.get("stderr")) + .then() + .statusCode(206) + .extract().asString(); + + Assertions.assertEquals("Goodb", body); + + body = getRequest() + .header("Range", "bytes=8-") + .get(taskLogs.get("stderr")) + .then() + .statusCode(206) + .extract().asString(); + + Assertions.assertEquals("Some sort of String\n", body); + + body = getRequest() + .header("Range", "bytes=1-3") + .get(taskLogs.get("stderr")) + .then() + .statusCode(206) + .extract().asString(); + + Assertions.assertEquals("ood", body); + + getRequest() + .header("Range", "bytes=1-3,6-9") + .get(taskLogs.get("stderr")) + .then() + .statusCode(416); + } + + + @ParameterizedTest + @MethodSource("completeSubmittedWorkflowWithoutFiles") + @DisplayName("Get Stdout and Stderr for task") + public void unauthenticatedUserCannotAccessLogs(String workflowJobId) { + Assumptions.assumeFalse(authType.equals(AuthType.NO_AUTH)); + + Map taskLogs = getJsonRequest() .get("/ga4gh/wes/v1/runs/{runId}", workflowJobId) .then() - .assertThat() - .statusCode(200) - .body("run_id",equalTo(workflowJobId)) - .body("state",equalTo("COMPLETE")) - .extract() - .jsonPath() - .getMap("task_logs[0]",String.class,String.class); - //@formatter:on - //@formatter:off - given() - .log().uri() - .log().method() + .assertThat() + .statusCode(200) + .body("run_id", equalTo(workflowJobId)) + .body("state", equalTo("COMPLETE")) + .extract() + .jsonPath() + .getMap("task_logs[0]", String.class, String.class); + + + given() + .log().uri() + .log().method() .get(taskLogs.get("stdout")) .then() - .statusCode(401); + .statusCode(401); - given() - .log().uri() - .log().method() + given() + .log().uri() + .log().method() .get(taskLogs.get("stderr")) .then() - .statusCode(401); - //@formatter:on + .statusCode(401); - } } + + private static void pollUntilJobCompletes(String workflowJobId) throws Exception { + waitForRunTerminalState(workflowJobId, "COMPLETE"); + } + + private static void waitForRunTerminalState(String workflowJobId, String expectedState) { + String actualState = pollInterval.until(() -> + getJsonRequest() + .get("/ga4gh/wes/v1/runs/{runId}/status", workflowJobId) + .then() + .statusCode(200) + .body("state", in(TERMINAL_STATES)) + .extract() + .jsonPath().getString("state") + , TERMINAL_STATES::contains); + + Assertions.assertEquals(expectedState, actualState, + "Expecting run %s to have a terminal state of '%s', however the state is '%s'".formatted(workflowJobId, expectedState, actualState)); + + } + + private static MultiPartSpecification getWorkflowUrlMultipart(String inputString) { + return new MultiPartSpecBuilder(inputString) + .controlName("workflow_url") + .mimeType(ContentType.TEXT.toString()) + .charset(StandardCharsets.UTF_8) + .emptyFileName() + .build(); + } + + private static MultiPartSpecification getJsonMultipart(String controlName, Map content) { + return new MultiPartSpecBuilder(content) + .controlName(controlName) + .mimeType(ContentType.JSON.toString()) + .charset(StandardCharsets.UTF_8) + .emptyFileName() + .build(); + } + + private static MultiPartSpecification getMultipartAttachment(String fileName, Map content) { + return new MultiPartSpecBuilder(content) + .controlName("workflow_attachment") + .fileName(fileName) + .mimeType(ContentType.JSON.toString()) + .charset(StandardCharsets.UTF_8) + .emptyFileName() + .build(); + } + + private static MultiPartSpecification getMultipartAttachment(String fileName, byte[] content) { + return new MultiPartSpecBuilder(content) + .controlName("workflow_attachment") + .mimeType(ContentType.BINARY.toString()) + .charset(StandardCharsets.UTF_8) + .fileName(fileName) + .build(); + } + } diff --git a/e2e-tests/src/test/java/com/dnastack/wes/service/config/CustomStrategy.java b/e2e-tests/src/test/java/com/dnastack/wes/service/config/CustomStrategy.java new file mode 100644 index 0000000..c769945 --- /dev/null +++ b/e2e-tests/src/test/java/com/dnastack/wes/service/config/CustomStrategy.java @@ -0,0 +1,45 @@ +package com.dnastack.wes.service.config; + +import com.dnastack.wes.service.util.EnvUtil; +import org.junit.platform.engine.ConfigurationParameters; +import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfiguration; +import org.junit.platform.engine.support.hierarchical.ParallelExecutionConfigurationStrategy; + + +public class CustomStrategy implements ParallelExecutionConfiguration, ParallelExecutionConfigurationStrategy { + + public static final int JUNIT_PARALLELISM = EnvUtil.optionalEnv("JUNIT_PARALLELISM", Math.min(Runtime.getRuntime().availableProcessors(), 8), + Integer::parseInt); + + @Override + public int getParallelism() { + return JUNIT_PARALLELISM; + } + + @Override + public int getMinimumRunnable() { + // if upgrading to java 17, this needs to be set to 0 as of workaround: https://github.com/SeleniumHQ/selenium/issues/10113 + return 0; + } + + @Override + public int getMaxPoolSize() { + return JUNIT_PARALLELISM; + } + + @Override + public int getCorePoolSize() { + return JUNIT_PARALLELISM; + } + + @Override + public int getKeepAliveSeconds() { + return 60; + } + + @Override + public ParallelExecutionConfiguration createConfiguration(final ConfigurationParameters configurationParameters) { + return this; + } + +} diff --git a/e2e-tests/src/test/java/com/dnastack/wes/service/util/EnvUtil.java b/e2e-tests/src/test/java/com/dnastack/wes/service/util/EnvUtil.java new file mode 100644 index 0000000..c83b455 --- /dev/null +++ b/e2e-tests/src/test/java/com/dnastack/wes/service/util/EnvUtil.java @@ -0,0 +1,40 @@ +package com.dnastack.wes.service.util; + +import static org.junit.jupiter.api.Assertions.fail; + +public final class EnvUtil { + + private EnvUtil() { + } + + + public static String requiredEnv(String name) { + String val = System.getenv(name); + if (val == null) { + fail("Environment variable `" + name + "` is required"); + } + return val; + } + + public static String optionalEnv(String name, String defaultValue) { + String val = System.getenv(name); + if (val == null) { + return defaultValue; + } + return val; + } + + public static T optionalEnv(String name, T defaultValue, Converter converter) { + String val = System.getenv(name); + if (val == null) { + return defaultValue; + } + return converter.convert(val); + } + + @FunctionalInterface + public interface Converter { + T convert(String value); + } + +} diff --git a/e2e-tests/src/test/resources/junit-platform.properties b/e2e-tests/src/test/resources/junit-platform.properties index f9df9ef..2205f25 100644 --- a/e2e-tests/src/test/resources/junit-platform.properties +++ b/e2e-tests/src/test/resources/junit-platform.properties @@ -1,5 +1,5 @@ junit.jupiter.execution.parallel.enabled = true -junit.jupiter.execution.parallel.config.strategy = fixed -junit.jupiter.execution.parallel.config.fixed.parallelism = 8 junit.jupiter.execution.parallel.mode.default = concurrent junit.jupiter.execution.parallel.mode.classes.default = concurrent +junit.jupiter.execution.parallel.config.strategy = custom +junit.jupiter.execution.parallel.config.custom.class = com.dnastack.wes.service.config.CustomStrategy