From f0f2495e9dbbaa5f64944c7560d3ae956b5cba7a Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Fri, 20 Feb 2026 16:05:44 +0100 Subject: [PATCH 1/3] [Update] First steps for exposing Snakemake generation of APE to RestAPE. --- CITATION.cff | 4 +- pom.xml | 22 ++++--- .../controller/RestApeController.java | 51 ++++++++++++--- .../controller/dto/APEConfig.java | 3 + .../controller/dto/SnakeFileInfo.java | 63 +++++++++++++++++++ .../dto/{CWLZip.java => WorkflowsZip.java} | 22 +++++-- .../nl/esciencecenter/models/Workflow.java | 3 + .../restape/APEWorkflowMetadata.java | 4 ++ .../nl/esciencecenter/restape/ApeAPI.java | 6 +- .../nl/esciencecenter/restape/IOUtils.java | 25 +++++--- .../esciencecenter/restape/RestApeUtils.java | 6 +- .../esciencecenter/restape/IOUtilsTest.java | 10 +-- 12 files changed, 179 insertions(+), 40 deletions(-) create mode 100644 src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java rename src/main/java/nl/esciencecenter/controller/dto/{CWLZip.java => WorkflowsZip.java} (80%) diff --git a/CITATION.cff b/CITATION.cff index af5746b..4fb1023 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -9,8 +9,8 @@ type: software authors: - given-names: Vedran family-names: Kasalica - email: v.kasalica@esciencecenter.nl - affiliation: Netherlands eScience Center, Netherlands + email: vedran.kasalica@health-ri.nl + affiliation: Health-RI, Netherlands orcid: 'https://orcid.org/0000-0002-0097-1056' - given-names: Peter family-names: Kok diff --git a/pom.xml b/pom.xml index 53622a9..c1c22b4 100644 --- a/pom.xml +++ b/pom.xml @@ -8,9 +8,9 @@ 3.0.4 - nl.esciencecenter + org.workflomics restape - 1.0.0 + 1.1.0 restape RESTful API for the APE library @@ -23,10 +23,16 @@ Vedran Kasalica - v.kasalica@esciencecenter.nl - Netherlands eScience Center - https://www.esciencecenter.nl/ + vedran.kasalica@health-ri.nl + Health-RI + https://www.health-ri.nl/ + + Mario Frank + mario.frank@uni-potsdam.de + University of Potsdam + https://www.uni-potsdam.de + scm:git:git://github.com/workflomics/restape.git @@ -86,11 +92,11 @@ runtime - + - io.github.sanctuuary + org.workflomics APE - 2.5.3 + 2.6.1 diff --git a/src/main/java/nl/esciencecenter/controller/RestApeController.java b/src/main/java/nl/esciencecenter/controller/RestApeController.java index 13a188f..75d5ec3 100644 --- a/src/main/java/nl/esciencecenter/controller/RestApeController.java +++ b/src/main/java/nl/esciencecenter/controller/RestApeController.java @@ -29,7 +29,8 @@ import io.swagger.v3.oas.annotations.Parameter; import nl.esciencecenter.controller.dto.APEConfig; import nl.esciencecenter.controller.dto.CWLFileInfo; -import nl.esciencecenter.controller.dto.CWLZip; +import nl.esciencecenter.controller.dto.SnakeFileInfo; +import nl.esciencecenter.controller.dto.WorkflowsZip; import nl.esciencecenter.controller.dto.ConstraintElem; import nl.esciencecenter.controller.dto.ImgFileInfo; import nl.esciencecenter.controller.dto.TaxonomyElem; @@ -349,7 +350,7 @@ public ResponseEntity postImage( description = "Retrieve a cwl file from the file system, describing the workflow.", tags = {"Download"}, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "JSON object containing the information for retrieving the CWL file, including the name of the CWL file (as 'figure_name') and the ID of the corresponding synthesis run.", + description = "JSON object containing the information for retrieving the CWL file, including the name of the CWL file (as 'cwl_name') and the ID of the corresponding synthesis run.", required = true, content = @Content( mediaType = "application/json", @@ -368,6 +369,40 @@ public ResponseEntity postCwl( .body(IOUtils.getLocalCwlFile(path)); } + /** + * Retrieve the solution CWL workflow based on the provided run ID and a + * candidate solution. + * + * @param fileName Name of the Snakemake file (provided under 'name' after the + * synthesis run). + * @param runID ID of the corresponding synthesis run (provided under + * 'run_id' after the synthesis run). + * @return Snakemake file representing the workflow. + */ + @PostMapping("/snakemake") + @Operation(summary = "Retrieve a Snakemake file", + description = "Retrieve a Snakemake file from the file system, describing the workflow.", + tags = {"Download"}, + requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( + description = "JSON object containing the information for retrieving the Snakemake file, including the name of the Snakemake file (as 'snakemake_name') and the ID of the corresponding synthesis run.", + required = true, + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = SnakeFileInfo.class)) + ), + responses = { + @ApiResponse(responseCode = "200", description = "Successful operation. Snakemake file describing the workflow is provided.", content = @Content(mediaType = "application/x-yaml")), + @ApiResponse(responseCode = "400", description = "Invalid input"), + @ApiResponse(responseCode = "404", description = "Not found") + }) + public ResponseEntity postSnakemake( + @RequestBody(required = true) SnakeFileInfo snakeInfoJson) throws IOException { + + Path path = snakeInfoJson.calculatePath(); + return ResponseEntity.ok().contentType(MediaType.parseMediaType("application/x-yaml")) + .body(IOUtils.getLocalSnakemakeFile(path)); + } + /** * Retrieve the CWL input file based on the provided run ID. * @@ -461,26 +496,26 @@ public ResponseEntity getBenchmarks( * @return CWL file representing the workflow. */ @PostMapping("/cwl_zip") - @Operation(summary = "Retrieve the zip of cwl files.", - description = "Retrieve the zip comprising CWL files specified in the request body. The request body should be a JSON object with the following fields: 'run_id' and 'workflows'. The 'run_id' field specifies the ID of the synthesis run, while the 'workflows' field is a list of CWL file names.", + @Operation(summary = "Retrieve the zip of cwl and Snakemake files.", + description = "Retrieve the zip comprising CWL and Snakemake files specified in the request body. The request body should be a JSON object with the following fields: 'run_id' and 'workflows'. The 'run_id' field specifies the ID of the synthesis run, while the 'workflows' field is a list of CWL and Snakemake file names.", tags = {"Download"}, requestBody = @io.swagger.v3.oas.annotations.parameters.RequestBody( description = "JSON object containing the following fields: 'run_id' and 'workflows'.", required = true, - content = @Content(schema = @Schema(implementation = CWLZip.class)) + content = @Content(schema = @Schema(implementation = WorkflowsZip.class)) ), responses = { @ApiResponse(responseCode = "200", - description = "Successful operation. A zip file comprising specified CWL files is returned.", + description = "Successful operation. A zip file comprising specified CWL and Snakemake files is returned.", content = @Content(mediaType = "application/zip")), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "404", description = "Not found"), @ApiResponse(responseCode = "500", description = "Internal server error") }) public ResponseEntity postZipCWLs( - @RequestBody(required = true) CWLZip cwlZipInfo) { + @RequestBody(required = true) WorkflowsZip workflowsZipInfo) { try { - Path zipPath = IOUtils.zipFilesForLocalExecution(cwlZipInfo); + Path zipPath = IOUtils.zipFilesForLocalExecution(workflowsZipInfo); Resource zipResource = new UrlResource(zipPath.toUri()); String zipContentType = Files.probeContentType(zipPath); diff --git a/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java b/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java index 5199ad0..2d0bf37 100644 --- a/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java +++ b/src/main/java/nl/esciencecenter/controller/dto/APEConfig.java @@ -50,6 +50,9 @@ public class APEConfig { @JsonProperty("number_of_cwl_files") private int numberOfCwlFiles; + @JsonProperty("number_of_snakemake_files") + private int numberOfSnakemakeFiles; + @JsonProperty("tool_seq_repeat") private boolean toolSeqRepeat; diff --git a/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java new file mode 100644 index 0000000..790e047 --- /dev/null +++ b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java @@ -0,0 +1,63 @@ +package nl.esciencecenter.controller.dto; + +import java.nio.file.Files; +import java.nio.file.Path; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import nl.esciencecenter.restape.RestApeUtils; + +@Getter +@NoArgsConstructor +public class SnakeFileInfo { + @JsonProperty("run_id") + private String runID; + + @JsonProperty("file_name") + private String fileName; + static final String snakemakeExtension = "snakefile"; + + + /** + * Set the runID in case it is valid, i.e., corresponds to runID formatting defined within RESTful APE. If the runID is not valid, an exception is thrown. + * + * @throws IllegalArgumentException If the structure is not valid. + */ + public void setRunID(String runID) throws IllegalArgumentException { + if (!RestApeUtils.isValidRunID(runID)) { + throw new IllegalArgumentException("The runID format '" + runID + "' is invalid."); + } + this.runID = runID; + } + + /** + * Set the file name in case it is valid, i.e., corresponds to file name formatting defined within APE and includes the supported Snakemake extension. + * If the file name is not valid, an exception is thrown. + * + * @throws IllegalArgumentException If the structure is not valid. + */ + public void setFileName(String fileName) throws IllegalArgumentException { + if (!RestApeUtils.isValidAPEFileName(fileName, snakemakeExtension)) { + throw new IllegalArgumentException("The Snakemake file_name format '" + fileName + "' is invalid."); + } + this.fileName = fileName; + } + + /** + * Get the path to the Snakemake file. If the file does not exist or is not accessible, an exception is thrown. + * + * @return Path to the Snakemake file. + */ + public Path calculatePath() { + Path path = RestApeUtils.calculatePath(runID, "Snakemake", fileName); + if(Files.notExists(path)) { + throw new IllegalArgumentException("The specified Snakemake file does not exist: " + path.toString()); + } else if (!Files.isReadable(path)) { + throw new IllegalArgumentException("The Snakemake file cannot be accessed: " + path.toString()); + } + return path; + } + +} diff --git a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java b/src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java similarity index 80% rename from src/main/java/nl/esciencecenter/controller/dto/CWLZip.java rename to src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java index 595c29f..97e7988 100644 --- a/src/main/java/nl/esciencecenter/controller/dto/CWLZip.java +++ b/src/main/java/nl/esciencecenter/controller/dto/WorkflowsZip.java @@ -19,7 +19,7 @@ @Getter @Setter @NoArgsConstructor -public class CWLZip { +public class WorkflowsZip { @JsonProperty("run_id") private String runID; private List workflows; @@ -51,7 +51,7 @@ public void setWorkflows(List workflows) throws IllegalArgumentException /** * Check whether all the workflows are valid CWL file names. - * @param workflows2 + * @param workflows The workflows to verify * * @return true if all the workflows are valid CWL file names, false otherwise. */ @@ -71,14 +71,26 @@ public List getCWLPaths() { } /** - * Get the paths to the CWL files and the SVG files. + * Get the paths to the Snakemake files. + * + * @return List of paths to the Snakemake files. + */ + public List getSnakemakePaths() { + return workflows.stream() + .map(fileName -> RestApeUtils.calculatePath(runID, "Snakemake", fileName)) + .collect(Collectors.toList()); + } + + /** + * Get the paths to the CWL, Snakemake and the SVG files. * - * @return List of paths to the CWL files and the SVG files. + * @return List of paths to the CWL, Snakemake and the SVG files. */ - public List getCWLandSVGPaths() { + public List getAllPaths() { List paths = new ArrayList<>(); for (String fileName : workflows) { paths.add(RestApeUtils.calculatePath(runID, "CWL", fileName)); + paths.add(RestApeUtils.calculatePath(runID, "Snakemake", fileName.replace(".cwl", ".smk"))); paths.add(RestApeUtils.calculatePath(runID, "Figures", fileName.replace(".cwl", ".svg"))); } return paths; diff --git a/src/main/java/nl/esciencecenter/models/Workflow.java b/src/main/java/nl/esciencecenter/models/Workflow.java index b6bd0d9..63b36b1 100644 --- a/src/main/java/nl/esciencecenter/models/Workflow.java +++ b/src/main/java/nl/esciencecenter/models/Workflow.java @@ -30,6 +30,9 @@ public class Workflow { @Column private String cwlFilePath; + @Column + private String snakemakeFilePath; + @Column private String pngFilePath; diff --git a/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java index 7304bce..240ae9d 100644 --- a/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java +++ b/src/main/java/nl/esciencecenter/restape/APEWorkflowMetadata.java @@ -30,6 +30,8 @@ public class APEWorkflowMetadata { private String runId; @JsonProperty("cwl_name") private String cwlName; + @JsonProperty("snakemake_name") + private String snakemakeName; @JsonProperty("figure_name") private String figureName; @JsonProperty("benchmark_file") @@ -49,6 +51,7 @@ public APEWorkflowMetadata(SolutionWorkflow sol, String runID, boolean benchmark this.workflowLength = sol.getSolutionLength(); this.runId = runID; this.cwlName = sol.getFileName() + ".cwl"; + this.snakemakeName = sol.getFileName() + ".smk"; this.figureName = sol.getFileName(); if (benchmark) { this.benchmarkFile = sol.getFileName() + ".json"; @@ -70,6 +73,7 @@ public JSONObject toJSONObject() { json.put("workflow_length", this.workflowLength); json.put("run_id", this.runId); json.put("cwl_name", this.cwlName); + json.put("snakemake_name", this.snakemakeName); json.put("figure_name", this.figureName); if (this.benchmarkFile != null) { json.put("benchmark_file", this.benchmarkFile); diff --git a/src/main/java/nl/esciencecenter/restape/ApeAPI.java b/src/main/java/nl/esciencecenter/restape/ApeAPI.java index ded7b17..14ac833 100644 --- a/src/main/java/nl/esciencecenter/restape/ApeAPI.java +++ b/src/main/java/nl/esciencecenter/restape/ApeAPI.java @@ -248,8 +248,9 @@ public static List runSynthesis(JSONObject configJson, bool SolutionsList candidateSolutions = executeSynthesis(configJson, runID); - // Write solutions (as CWL files and figures) to the file system. - APE.writeCWLWorkflows(candidateSolutions); + // Write solutions (as CWL and Snakemake files and figures) to the file system. + APE.writeCWLWorkflows(candidateSolutions, true); + APE.writeSnakemakeWorkflows(candidateSolutions, true); APE.writeTavernaDesignGraphs(candidateSolutions, Format.SVG); APE.writeTavernaDesignGraphs(candidateSolutions, Format.PNG); @@ -287,6 +288,7 @@ private static SolutionsList executeSynthesis(JSONObject configJson, String runI runConfig.setSolutionPath(solutionPath); int maxSol = runConfig.getMaxNoSolutions(); runConfig.setNoCWL(maxSol); + runConfig.setNoSnakemake(maxSol); runConfig.setNoGraphs(maxSol); runConfig.setDebugMode(true); // run the synthesis and retrieve the solutions diff --git a/src/main/java/nl/esciencecenter/restape/IOUtils.java b/src/main/java/nl/esciencecenter/restape/IOUtils.java index 865fddc..5fac093 100644 --- a/src/main/java/nl/esciencecenter/restape/IOUtils.java +++ b/src/main/java/nl/esciencecenter/restape/IOUtils.java @@ -18,7 +18,7 @@ import lombok.AccessLevel; import lombok.NoArgsConstructor; -import nl.esciencecenter.controller.dto.CWLZip; +import nl.esciencecenter.controller.dto.WorkflowsZip; /** * The {@code IOUtils} class provides static methods to read the input files. @@ -42,6 +42,17 @@ public static String getLocalCwlFile(Path filePath) throws IOException, NoSuchFi return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); } + /** + * Get the Snakemake content of the file at the given path. + * + * @param filePath - path to the Snakemake file + * @return Snakemake content of the file representing a workflow + * @throws IOException - if the file cannot be read + */ + public static String getLocalSnakemakeFile(Path filePath) throws IOException, NoSuchFileException { + return FileUtils.readFileToString(filePath.toFile(), StandardCharsets.UTF_8); + } + /** * Get the JSON content of the file at the given path. * @@ -58,24 +69,24 @@ public static String getLocalBenchmarkFile(Path filePath) throws IOException { * a single zip file. In addition, a `readme.txt` file with instructions on how * to run the workflows is added to the zip. * - * @param cwlZipInfo - the CWL zip information, containing the runID and the list of workflow file names. + * @param workflowsZipInfo - the CWL zip information, containing the runID and the list of workflow file names. * * @return Path to the created zip file. * @throws IOException Error is thrown if the zip file cannot be created or * written to. */ - public static Path zipFilesForLocalExecution(CWLZip cwlZipInfo) throws IOException { + public static Path zipFilesForLocalExecution(WorkflowsZip workflowsZipInfo) throws IOException { - List cwlFilePaths = cwlZipInfo.getCWLandSVGPaths(); + List filePaths = workflowsZipInfo.getAllPaths(); // Add the CWL input file to the zip - Path cwlInputPath = RestApeUtils.calculatePath(cwlZipInfo.getRunID(), "CWL", "input.yml"); - cwlFilePaths.add(cwlInputPath); + Path cwlInputPath = RestApeUtils.calculatePath(workflowsZipInfo.getRunID(), "CWL", "input.yml"); + filePaths.add(cwlInputPath); Path zipPath = cwlInputPath.getParent().resolve("workflows.zip"); try (FileOutputStream fos = new FileOutputStream(zipPath.toFile()); ZipOutputStream zipOut = new ZipOutputStream(fos)) { - for (Path file : cwlFilePaths) { + for (Path file : filePaths) { zipOut.putNextEntry(new ZipEntry(file.getFileName().toString())); Files.copy(file, zipOut); zipOut.closeEntry(); diff --git a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java index 39f876f..69f7982 100644 --- a/src/main/java/nl/esciencecenter/restape/RestApeUtils.java +++ b/src/main/java/nl/esciencecenter/restape/RestApeUtils.java @@ -131,13 +131,13 @@ public static boolean isValidAPEFileName(String fileName, String extension) { /** * Checks whether the subdirectory generated by {@link APE} is valid, by - * checking its name. The subdirectory could be either `CWL` or `Figures`. + * checking its name. The subdirectory could be either `CWL`, Snakemake or `Figures`. * * @param fileSubDir - subdirectory to be verified * @return true if the subdirectory is valid, false otherwise. */ private static boolean isValidAPESubDir(String fileSubDir) { - return fileSubDir.equals("CWL") || fileSubDir.equals("Figures"); + return fileSubDir.equals("CWL") || fileSubDir.equals("Snakemake") || fileSubDir.equals("Figures"); } /** @@ -186,7 +186,7 @@ public static Path calculatePath(String runID, String fileSubDir, String fileNam isValidAPESubDir(fileSubDir)) { return Paths.get(getSolutionPath(), runID, fileSubDir, fileName); } else { - throw new IllegalArgumentException("The provided runID, fileSubDir or fileName is not valid."); + throw new IllegalArgumentException(String.format("The provided runID (%s), fileSubDir (%s) or fileName (%s) is not valid.", runID, fileSubDir, fileName)); } } diff --git a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java index 34e0853..7bdbcd5 100644 --- a/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java +++ b/src/test/java/nl/esciencecenter/restape/IOUtilsTest.java @@ -9,7 +9,7 @@ import org.json.JSONObject; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import nl.esciencecenter.controller.dto.CWLZip; +import nl.esciencecenter.controller.dto.WorkflowsZip; import nl.uu.cs.ape.utils.APEFiles; @SpringBootTest @@ -29,10 +29,10 @@ void testZipFilesForLocalExecution() throws Exception { String runID = result.get(0).getRunId(); String cwlFile = result.get(0).getCwlName(); - CWLZip cwlZip = new CWLZip(); - cwlZip.setRunID(runID); - cwlZip.setWorkflows(List.of(cwlFile)); + WorkflowsZip workflowsZip = new WorkflowsZip(); + workflowsZip.setRunID(runID); + workflowsZip.setWorkflows(List.of(cwlFile)); - IOUtils.zipFilesForLocalExecution(cwlZip); + IOUtils.zipFilesForLocalExecution(workflowsZip); } } From f3ab879c82db949f29d8d4ca7419695dbb17f535 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Fri, 20 Feb 2026 17:06:58 +0100 Subject: [PATCH 2/3] [FIX] Correct the Snakefile extension in the DTO. --- .../java/nl/esciencecenter/controller/dto/SnakeFileInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java index 790e047..d104739 100644 --- a/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java +++ b/src/main/java/nl/esciencecenter/controller/dto/SnakeFileInfo.java @@ -17,7 +17,7 @@ public class SnakeFileInfo { @JsonProperty("file_name") private String fileName; - static final String snakemakeExtension = "snakefile"; + static final String snakemakeExtension = "smk"; /** From ab21ef8a8f72e331f9dca0c3610c6a5ab23cf517 Mon Sep 17 00:00:00 2001 From: Mario Frank Date: Mon, 23 Feb 2026 12:03:35 +0100 Subject: [PATCH 3/3] [UPDATE] Add test for snakemake controller method. --- .../controller/RestApeControllerTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java index 020a455..ece98e2 100644 --- a/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java +++ b/src/test/java/nl/esciencecenter/controller/RestApeControllerTest.java @@ -131,6 +131,37 @@ void testRunSynthesisPass() throws Exception { .andExpect(status().isOk()); } + /** + * Test the snakemake method with POST and a valid configuration file. + * + * @throws Exception + */ + @Test + void testRunSnakemakeExtraction() throws Exception { + + String configPath = "https://raw.githubusercontent.com/Workflomics/tools-and-domains/refs/heads/main/domains/proteomics/config.json"; + String content = FileUtils.readFileToString(APEFiles.readPathToFile(configPath), + StandardCharsets.UTF_8); + + JSONObject jsonObject = new JSONObject(content); + jsonObject.put("solutions", "1"); + jsonObject.put("number_of_snakemake_files", "10"); + + List result = ApeAPI.runSynthesis(jsonObject, false); + assertFalse(result.isEmpty(), "The encoding should be SAT."); + String runID = result.get(0).getRunId(); + String snakeFile = result.get(0).getSnakemakeName(); + + String jsonContent = "{\"run_id\": \"" + runID + "\", \"file_name\": \"" + snakeFile + "\"}"; + + mvc.perform(MockMvcRequestBuilders.post("/snakemake") + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON).content(jsonContent)) + .andExpect(status().isOk()) + .andExpect(content().contentType("application/x-yaml")); + + } + @Test void testPostZipCWLs() throws Exception {