diff --git a/pom.xml b/pom.xml index 700dbfc..930cc4b 100644 --- a/pom.xml +++ b/pom.xml @@ -44,16 +44,16 @@ 1.8 - 1.16.2 - 2.3.1 + 1.18.0 + 2.10.3 21.0 1.2.17 4.11 1.9.5 - 1.16.2.0 - 3.1 + 1.18.0.0 + 3.8.1 2.4 0.7.3.201502191951 3.0.0-M1 diff --git a/src/main/java/com/codepine/api/testrail/Request.java b/src/main/java/com/codepine/api/testrail/Request.java index 219a859..2b05979 100644 --- a/src/main/java/com/codepine/api/testrail/Request.java +++ b/src/main/java/com/codepine/api/testrail/Request.java @@ -44,7 +44,6 @@ import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j; -import javax.xml.bind.DatatypeConverter; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.IOException; @@ -54,7 +53,10 @@ import java.lang.reflect.Type; import java.net.HttpURLConnection; import java.net.MalformedURLException; -import java.nio.charset.Charset; +import java.util.Base64; + +import static com.codepine.api.testrail.Request.Method.POST; +import static com.codepine.api.testrail.internal.FileHelper.createFileFromIStream; /** * TestRail request. @@ -117,17 +119,15 @@ public T execute() { if (config.getApplicationName().isPresent()) { con.setRequestProperty("User-Agent", config.getApplicationName().get()); } - con.setRequestProperty("Content-Type", "application/json"); - String basicAuth = "Basic " - + DatatypeConverter.printBase64Binary((config.getUsername() - + ":" + config.getPassword()).getBytes(Charset.forName("UTF-8"))); - con.setRequestProperty("Authorization", basicAuth); - if (method == Method.POST) { + con.addRequestProperty("Content-Type", getContentType()); + String auth = getAuthorization(config.getUsername(), config.getPassword()); + con.addRequestProperty("Authorization", "Basic " + auth); + if (method == POST) { con.setDoOutput(true); Object content = getContent(); if (content != null) { - try (OutputStream outputStream = new BufferedOutputStream(con.getOutputStream())) { - JSON.writerWithView(this.getClass()).writeValue(outputStream, content); + try (OutputStream outputStream = con.getOutputStream()) { + write(content, outputStream); } } else { con.setFixedLengthStreamingMode(0); @@ -159,6 +159,11 @@ public T execute() { if (responseClass == Void.class) { return null; } + if (responseClass == String.class) { + String filePath = (String) getContent(); + createFileFromIStream(responseStream, filePath); + return (T) filePath; + } if (supplementForDeserialization != null) { return JSON.reader(responseClass).with(new InjectableValues.Std().addValue(responseClass.toString(), supplementForDeserialization)).readValue(responseStream); } @@ -203,6 +208,18 @@ private String getUrl() throws IOException { return urlBuilder.toString(); } + /** + * Override this method to body to be send with {@code Method#POST} requests. + * + * @param content content to be send with {@code Method#POST} + * @param conOutputStream the HttpURLConnection output stream + */ + void write(final Object content, final OutputStream conOutputStream) throws IOException { + try (OutputStream outputStream = new BufferedOutputStream(conOutputStream)) { + JSON.writerWithView(this.getClass()).writeValue(outputStream, content); + } + } + /** * Override this method to provide content to be send with {@code Method#POST} requests. * @@ -230,11 +247,30 @@ void setUrlConnectionFactory(UrlConnectionFactory urlConnectionFactory) { this.urlConnectionFactory = urlConnectionFactory; } + /** + * Override this method to change Content-Type to be send with {@code Method#POST} requests headers. + * + * @return content + */ + String getContentType() { + return "application/json"; + } + /** * Allowed HTTP methods. */ - static enum Method { + enum Method { GET, POST; } + private static String getAuthorization(String user, String password) { + try { + return new String(Base64.getEncoder().encode((user + ":" + password).getBytes())); + } catch (IllegalArgumentException e) { + // Not thrown + } + + return ""; + } + } diff --git a/src/main/java/com/codepine/api/testrail/TestRail.java b/src/main/java/com/codepine/api/testrail/TestRail.java index 4d87ed1..6d67676 100644 --- a/src/main/java/com/codepine/api/testrail/TestRail.java +++ b/src/main/java/com/codepine/api/testrail/TestRail.java @@ -26,6 +26,8 @@ import com.codepine.api.testrail.internal.BooleanToIntSerializer; import com.codepine.api.testrail.internal.ListToCsvSerializer; +import com.codepine.api.testrail.model.Attachment; +import com.codepine.api.testrail.model.AttachmentId; import com.codepine.api.testrail.model.Case; import com.codepine.api.testrail.model.CaseField; import com.codepine.api.testrail.model.CaseType; @@ -53,9 +55,16 @@ import lombok.Setter; import lombok.experimental.Accessors; +import java.io.BufferedWriter; +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.util.Date; +import static com.codepine.api.testrail.internal.FileHelper.writeFileInOStream; import static com.google.common.base.Preconditions.checkArgument; +import static java.util.Objects.nonNull; /** * Client for Test Rail API. Configure and use it to create requests for the API. @@ -226,6 +235,15 @@ public Results results() { return new Results(); } + /** + * An accessor for creating attachments for "Attachments". + * + * @return a request factory + */ + public Attachments attachments() { + return new Attachments(); + } + /** * Builder for {@code TestRail}. */ @@ -2071,4 +2089,165 @@ private List() { } } + + /** + * Request factories for "Attachments". + */ + @NoArgsConstructor + public class Attachments { + + /** + * Adds an attachment inside result. + * + * @param resultId the ID of the result where to add attachment + * @param filePath the path of the file + * @return the request + * @throws java.lang.IllegalArgumentException if resultId is not positive or file path is empty or null + * @throws java.lang.NullPointerException if any other argument is null + */ + public Add add(final int resultId, String filePath) { + checkArgument(resultId > 0, "resultId should be positive"); + checkArgument(!filePath.isEmpty() && nonNull(filePath), "specify filePath"); + return new Attachments.Add(resultId, filePath); + } + + /** + * Returns a list of attachments for a case id. + * + * @param caseId the ID of the case to get the attachments for + * @return the request + * @throws java.lang.IllegalArgumentException if caseId is not positive + */ + public Attachments.ListForCase listForCase(final int caseId) { + checkArgument(caseId > 0, "caseId should be positive"); + return new Attachments.ListForCase(caseId); + } + + /** + * Returns a list of attachments for a test id. + * + * @param testId the ID of the case to get the attachments for + * @return the request + * @throws java.lang.IllegalArgumentException if testId is not positive + */ + public Attachments.ListForTest listForTest(final int testId) { + checkArgument(testId > 0, "testId should be positive"); + return new Attachments.ListForTest(testId); + } + + /** + * Returns attachment path where it is put. + * + * @param attachmentId the ID of the attachment + * @param filePath the path of the file where attachment will be put + * @return the request + * @throws java.lang.IllegalArgumentException if attachmentId is not positive or file path is empty or null + */ + public Attachments.Get get(final int attachmentId, String filePath) { + checkArgument(attachmentId > 0, "attachmentId should be positive"); + checkArgument(!filePath.isEmpty() && nonNull(filePath), "specify filePath"); + return new Attachments.Get(attachmentId, filePath); + } + + /** + * Deletes an existing attachment. + * + * @param attachmentId the ID of the attachment to be deleted + * @return the request + * @throws java.lang.IllegalArgumentException if attachmentId is not positive + */ + public Attachments.Delete delete(final int attachmentId) { + checkArgument(attachmentId > 0, "attachmentId should be positive"); + return new Attachments.Delete(attachmentId); + } + + public class Add extends Request { + private static final String REST_PATH = "add_attachment_to_result/"; + + private final String boundary = "TestRailAPIAttachmentBoundary"; + + private File uploadFile; + + private Add(int resultId, String filePath) { + super(config, Method.POST, REST_PATH + resultId, AttachmentId.class); + this.uploadFile = new File(filePath); + } + + @Override + void write(final Object content, final OutputStream oStream) throws IOException { + File file = (File) content; + + try (BufferedWriter bodyWriter = new BufferedWriter(new OutputStreamWriter(oStream))) { + bodyWriter.write("\n\n--" + boundary + "\r\n" + + "Content-Disposition: form-data; name=\"attachment\"; filename=\"" + file.getName() + + "\"" + "\r\n\r\n"); + bodyWriter.flush(); + + writeFileInOStream(oStream, file); + bodyWriter.flush(); + + bodyWriter.write("\r\n--" + boundary + "--\r\n"); + bodyWriter.flush(); + } + } + + @Override + Object getContent() { + return uploadFile; + } + + @Override + String getContentType() { + return "multipart/form-data; boundary=" + boundary; + } + } + + @Getter + @Setter + public class ListForCase extends Request> { + private static final String REST_PATH = "get_attachments_for_case/"; + + private ListForCase(int caseId) { + super(config, Method.GET, REST_PATH + caseId, new TypeReference>() { + }); + } + } + + @Getter + @Setter + public class ListForTest extends Request> { + private static final String REST_PATH = "get_attachments_for_test/"; + + private ListForTest(int testId) { + super(config, Method.GET, REST_PATH + testId, new TypeReference>() { + }); + } + } + + public class Get extends Request { + private static final String REST_PATH = "get_attachment/"; + + private String filePath; + + private Get(int attachmentId, String filePath) { + super(config, Method.GET, REST_PATH + attachmentId, String.class); + this.filePath = filePath; + } + + @Override + Object getContent() { + return filePath; + } + } + + public class Delete extends Request { + private static final String REST_PATH = "delete_attachment/"; + + private Delete(int attachmentId) { + super(config, Method.POST, REST_PATH + attachmentId, Void.class); + } + + } + + } } \ No newline at end of file diff --git a/src/main/java/com/codepine/api/testrail/internal/FileHelper.java b/src/main/java/com/codepine/api/testrail/internal/FileHelper.java new file mode 100644 index 0000000..7fd9214 --- /dev/null +++ b/src/main/java/com/codepine/api/testrail/internal/FileHelper.java @@ -0,0 +1,31 @@ +package com.codepine.api.testrail.internal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class FileHelper { + + public static void writeFileInOStream(OutputStream outputStream, File file) throws IOException { + try (InputStream inputStream = new FileInputStream(file)) { + int bytesRead; + byte[] dataBuffer = new byte[1024]; + while ((bytesRead = inputStream.read(dataBuffer)) != -1) { + outputStream.write(dataBuffer, 0, bytesRead); + } + } + } + + public static void createFileFromIStream(InputStream inputStream, String filePath) throws IOException { + try (FileOutputStream outputStream = new FileOutputStream(filePath)) { + int bytesRead = 0; + byte[] buffer = new byte[1024]; + while ((bytesRead = inputStream.read(buffer)) > 0) { + outputStream.write(buffer, 0, bytesRead); + } + } + } +} diff --git a/src/main/java/com/codepine/api/testrail/model/Attachment.java b/src/main/java/com/codepine/api/testrail/model/Attachment.java new file mode 100644 index 0000000..9452998 --- /dev/null +++ b/src/main/java/com/codepine/api/testrail/model/Attachment.java @@ -0,0 +1,57 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Kunal Shah + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.codepine.api.testrail.model; + +import lombok.Data; + +import java.util.Date; + +/** + * TestRail attachment. + */ +@Data +public class Attachment { + + private int id; + + private String name; + + private String fileName; + + private int size; + + private Date created_on; + + private int project_id; + + private int case_id; + + private int test_change_id; + + private int user_id; + + private int result_id; + +} \ No newline at end of file diff --git a/src/main/java/com/codepine/api/testrail/model/AttachmentId.java b/src/main/java/com/codepine/api/testrail/model/AttachmentId.java new file mode 100644 index 0000000..7a042c6 --- /dev/null +++ b/src/main/java/com/codepine/api/testrail/model/AttachmentId.java @@ -0,0 +1,37 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2015 Kunal Shah + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package com.codepine.api.testrail.model; + +import lombok.Data; + +/** + * TestRail attachment Id. + */ +@Data +public class AttachmentId { + + private int attachment_id; + +} \ No newline at end of file