From ce571722d5727f51b1bc66efc7ce64b470b88266 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Heffner?= <56156750+gitgoodjhe@users.noreply.github.com> Date: Wed, 18 Mar 2020 14:26:35 +0100 Subject: [PATCH] TSK-1150: Added REST-API for task comments --- .../task/internal/TaskCommentMapper.java | 1 + .../sql/sample-data/task-comment.sql | 17 +- .../main/java/pro/taskana/rest/Mapping.java | 2 + .../taskana/rest/TaskCommentController.java | 164 ++++++++++++++ .../resource/TaskCommentListResource.java | 24 +++ .../rest/resource/TaskCommentResource.java | 92 ++++++++ .../TaskCommentResourceAssembler.java | 79 +++++++ .../src/test/java/pro/taskana/RestHelper.java | 7 + ...askCommentControllerRestDocumentation.java | 202 ++++++++++++++++++ .../rest/TaskCommentControllerIntTest.java | 182 ++++++++++++++++ .../src/test/resources/asciidoc/rest-api.adoc | 91 ++++++++ 11 files changed, 860 insertions(+), 1 deletion(-) create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskCommentController.java create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentListResource.java create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResource.java create mode 100644 rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResourceAssembler.java create mode 100644 rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskCommentControllerRestDocumentation.java create mode 100644 rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskCommentControllerIntTest.java diff --git a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskCommentMapper.java b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskCommentMapper.java index d79fad2a4..ca39f0e8b 100644 --- a/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskCommentMapper.java +++ b/lib/taskana-core/src/main/java/pro/taskana/task/internal/TaskCommentMapper.java @@ -31,6 +31,7 @@ public interface TaskCommentMapper { "") @Results( diff --git a/lib/taskana-data/src/main/resources/sql/sample-data/task-comment.sql b/lib/taskana-data/src/main/resources/sql/sample-data/task-comment.sql index d06191059..a40cd3ae5 100644 --- a/lib/taskana-data/src/main/resources/sql/sample-data/task-comment.sql +++ b/lib/taskana-data/src/main/resources/sql/sample-data/task-comment.sql @@ -1,3 +1,18 @@ -- TASK_COMMENT TABLE ID , TASK_ID ,TEXTFIELD ,CREATOR ,COMPLETED ,MODIFIED + +-- TaskComments for GetTaskCommentAccTest + UpdateTaskCommentAccTest INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000000', 'TKI:000000000000000000000000000000000000', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); -INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000001', 'TKI:000000000000000000000000000000000000', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); \ No newline at end of file +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000001', 'TKI:000000000000000000000000000000000000', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000002', 'TKI:000000000000000000000000000000000000', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000003', 'TKI:000000000000000000000000000000000025', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +-- TaskComments for DeleteTaskCommentAccTest +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000004', 'TKI:000000000000000000000000000000000001', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000005', 'TKI:000000000000000000000000000000000001', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000006', 'TKI:000000000000000000000000000000000002', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000007', 'TKI:000000000000000000000000000000000002', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +-- TaskComments for CreateTaskCommentAccTest +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000008', 'TKI:000000000000000000000000000000000026', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000009', 'TKI:000000000000000000000000000000000026', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000010', 'TKI:000000000000000000000000000000000027', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000011', 'TKI:000000000000000000000000000000000027', 'some other text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); +INSERT INTO TASK_COMMENT VALUES('TCI:000000000000000000000000000000000012', 'TKI:000000000000000000000000000000000004', 'some text in textfield', 'user_1_1', '2018-01-29 15:55:00', '2018-01-30 15:55:00'); \ No newline at end of file diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java index 623799d57..28a97066b 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/Mapping.java @@ -25,6 +25,8 @@ public final class Mapping { public static final String URL_VERSION = PRE + "version"; public static final String URL_TASKS = PRE + "tasks"; public static final String URL_TASKS_ID = URL_TASKS + "/{taskId}"; + public static final String URL_TASK_COMMENTS = URL_TASKS_ID + "/comments"; + public static final String URL_TASK_COMMENT = URL_TASK_COMMENTS + "/{taskCommentId}"; public static final String URL_TASKS_ID_CLAIM = URL_TASKS_ID + "/claim"; public static final String URL_TASKS_ID_COMPLETE = URL_TASKS_ID + "/complete"; public static final String URL_TASKS_ID_TRANSFER_WORKBASKETID = diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskCommentController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskCommentController.java new file mode 100644 index 000000000..79dab09de --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskCommentController.java @@ -0,0 +1,164 @@ +package pro.taskana.rest; + +import java.util.List; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.hateoas.config.EnableHypermediaSupport; +import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +import pro.taskana.common.api.exceptions.ConcurrencyException; +import pro.taskana.common.api.exceptions.InvalidArgumentException; +import pro.taskana.common.api.exceptions.NotAuthorizedException; +import pro.taskana.rest.resource.TaskCommentListResource; +import pro.taskana.rest.resource.TaskCommentResource; +import pro.taskana.rest.resource.TaskCommentResourceAssembler; +import pro.taskana.task.api.TaskService; +import pro.taskana.task.api.exceptions.TaskCommentNotFoundException; +import pro.taskana.task.api.exceptions.TaskNotFoundException; +import pro.taskana.task.api.models.TaskComment; + +/** Controller for all {@link TaskComment} related endpoints. */ +@RestController +@EnableHypermediaSupport(type = HypermediaType.HAL) +public class TaskCommentController { + + private static final Logger LOGGER = LoggerFactory.getLogger(TaskCommentController.class); + + private TaskService taskService; + private TaskCommentResourceAssembler taskCommentResourceAssembler; + + TaskCommentController( + TaskService taskService, TaskCommentResourceAssembler taskCommentResourceAssembler) { + this.taskService = taskService; + this.taskCommentResourceAssembler = taskCommentResourceAssembler; + } + + + @GetMapping(path = Mapping.URL_TASK_COMMENT) + @Transactional(readOnly = true, rollbackFor = Exception.class) + public ResponseEntity getTaskComment(@PathVariable String taskCommentId) + throws NotAuthorizedException, TaskNotFoundException, TaskCommentNotFoundException, + InvalidArgumentException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Entry to getTaskComment(taskCommentId= {})", taskCommentId); + } + + TaskComment taskComment = taskService.getTaskComment(taskCommentId); + + TaskCommentResource taskCommentResource = taskCommentResourceAssembler.toResource(taskComment); + + ResponseEntity response = ResponseEntity.ok(taskCommentResource); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from getTaskComment(), returning {}", response); + } + + return response; + } + + @GetMapping(path = Mapping.URL_TASK_COMMENTS) + @Transactional(readOnly = true, rollbackFor = Exception.class) + public ResponseEntity getTaskComments(@PathVariable String taskId) + throws NotAuthorizedException, TaskNotFoundException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Entry to getTaskComments(taskId= {})", taskId); + } + + List taskComments = taskService.getTaskComments(taskId); + + TaskCommentListResource taskCommentListResource = + taskCommentResourceAssembler.toListResource(taskComments); + + ResponseEntity response = ResponseEntity.ok(taskCommentListResource); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from getTaskComments(), returning {}", response); + } + + return response; + } + + @DeleteMapping(path = Mapping.URL_TASK_COMMENT) + @Transactional(readOnly = true, rollbackFor = Exception.class) + public ResponseEntity deleteTaskComment(@PathVariable String taskCommentId) + throws NotAuthorizedException, TaskNotFoundException, TaskCommentNotFoundException, + InvalidArgumentException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Entry to deleteTaskComment(taskCommentId= {})", taskCommentId); + } + + taskService.deleteTaskComment(taskCommentId); + + ResponseEntity result = ResponseEntity.noContent().build(); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from deleteTaskComment(), returning {}", result); + } + + return result; + } + + @PutMapping(path = Mapping.URL_TASK_COMMENT) + @Transactional(readOnly = true, rollbackFor = Exception.class) + public ResponseEntity updateTaskComment( + @PathVariable String taskCommentId, @RequestBody TaskCommentResource taskCommentResource) + throws NotAuthorizedException, TaskNotFoundException, TaskCommentNotFoundException, + InvalidArgumentException, ConcurrencyException { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Entry to updateTaskComment(taskCommentId= {})", taskCommentId); + } + + ResponseEntity result; + if (taskCommentId.equals(taskCommentResource.getTaskCommentId())) { + TaskComment taskComment = taskCommentResourceAssembler.toModel(taskCommentResource); + + taskComment = taskService.updateTaskComment(taskComment); + result = ResponseEntity.ok(taskCommentResourceAssembler.toResource(taskComment)); + } else { + throw new InvalidArgumentException( + String.format( + "TaskCommentId ('%s') is not identical with the taskCommentId of " + + "object in the payload which should be updated. ID=('%s')", + taskCommentId, taskCommentResource.getTaskId())); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from updateTaskComment(), returning {}", result); + } + + return result; + } + + @PostMapping(path = Mapping.URL_TASK_COMMENTS) + @Transactional(rollbackFor = Exception.class) + public ResponseEntity createTaskComment( + @RequestBody TaskCommentResource taskCommentResource) + throws NotAuthorizedException, InvalidArgumentException, TaskNotFoundException { + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Entry to createTaskComment(taskCommentResource= {})", taskCommentResource); + } + + TaskComment taskCommentFromResource = taskCommentResourceAssembler.toModel(taskCommentResource); + TaskComment createdTaskComment = taskService.createTaskComment(taskCommentFromResource); + + ResponseEntity result = + ResponseEntity.status(HttpStatus.CREATED) + .body(taskCommentResourceAssembler.toResource(createdTaskComment)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from createTaskComment(), returning {}", result); + } + + return result; + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentListResource.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentListResource.java new file mode 100644 index 000000000..3fcd79dbd --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentListResource.java @@ -0,0 +1,24 @@ +package pro.taskana.rest.resource; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import org.springframework.hateoas.ResourceSupport; + +/** Resource class for {@link TaskCommentResource} with Pagination. */ +public class TaskCommentListResource extends ResourceSupport { + + private List content; + + public TaskCommentListResource() { + super(); + } + + public TaskCommentListResource(List taskCommentResources) { + this.content = taskCommentResources; + } + + @JsonProperty("task comments") + public List getContent() { + return content; + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResource.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResource.java new file mode 100644 index 000000000..a24ce29c1 --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResource.java @@ -0,0 +1,92 @@ +package pro.taskana.rest.resource; + +import org.springframework.hateoas.ResourceSupport; + +import pro.taskana.task.api.models.TaskComment; + +/** Resource class for {@link TaskComment}. */ +public class TaskCommentResource extends ResourceSupport { + + private String taskCommentId; + private String taskId; + private String textField; + private String creator; + private String created; + private String modified; + + public TaskCommentResource() {} + + public TaskCommentResource(TaskComment taskComment) { + this.taskCommentId = taskComment.getId(); + this.taskId = taskComment.getTaskId(); + this.textField = taskComment.getTextField(); + this.creator = taskComment.getCreator(); + this.created = taskComment.getCreated().toString(); + this.modified = taskComment.getModified().toString(); + } + + public String getTaskCommentId() { + return taskCommentId; + } + + public void setTaskCommentId(String id) { + this.taskCommentId = id; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + public String getCreator() { + return creator; + } + + public void setCreator(String creator) { + this.creator = creator; + } + + public String getTextField() { + return textField; + } + + public void setTextField(String textField) { + this.textField = textField; + } + + public String getCreated() { + return created; + } + + public void setCreated(String created) { + this.created = created; + } + + public String getModified() { + return modified; + } + + public void setModified(String modified) { + this.modified = modified; + } + + @Override + public String toString() { + return "TaskCommentResource [taskCommentId=" + + taskCommentId + + ", taskId=" + + taskId + + ", textField=" + + textField + + ", creator=" + + creator + + ", created=" + + created + + ", modified=" + + modified + + "]"; + } +} diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResourceAssembler.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResourceAssembler.java new file mode 100644 index 000000000..9891a3016 --- /dev/null +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/resource/TaskCommentResourceAssembler.java @@ -0,0 +1,79 @@ +package pro.taskana.rest.resource; + +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo; +import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn; + +import java.time.Instant; +import java.util.List; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.mvc.ResourceAssemblerSupport; +import org.springframework.stereotype.Component; + +import pro.taskana.common.api.exceptions.InvalidArgumentException; +import pro.taskana.common.api.exceptions.NotAuthorizedException; +import pro.taskana.common.api.exceptions.SystemException; +import pro.taskana.rest.Mapping; +import pro.taskana.rest.TaskCommentController; +import pro.taskana.rest.resource.links.PageLinks; +import pro.taskana.task.api.TaskService; +import pro.taskana.task.api.exceptions.TaskCommentNotFoundException; +import pro.taskana.task.api.exceptions.TaskNotFoundException; +import pro.taskana.task.api.models.TaskComment; +import pro.taskana.task.internal.models.TaskCommentImpl; + +/** Resource assembler for {@link TaskCommentResource}. */ +@Component +public class TaskCommentResourceAssembler + extends ResourceAssemblerSupport { + + private final TaskService taskService; + + @Autowired + public TaskCommentResourceAssembler(TaskService taskService) { + super(TaskCommentController.class, TaskCommentResource.class); + this.taskService = taskService; + } + + @Override + public TaskCommentResource toResource(TaskComment taskComment) { + + TaskCommentResource taskCommentResource = new TaskCommentResource(taskComment); + try { + taskCommentResource.add( + linkTo(methodOn(TaskCommentController.class).getTaskComment(taskComment.getId())) + .withSelfRel()); + } catch (TaskCommentNotFoundException + | TaskNotFoundException + | NotAuthorizedException + | InvalidArgumentException e) { + throw new SystemException("caught unexpected Exception.", e.getCause()); + } + + return taskCommentResource; + } + + @PageLinks(Mapping.URL_TASK_COMMENTS) + public TaskCommentListResource toListResource( + List taskComments) { + return new TaskCommentListResource(toResources(taskComments)); + } + + public TaskComment toModel(TaskCommentResource taskCommentResource) { + + TaskCommentImpl taskComment = + (TaskCommentImpl) taskService.newTaskComment(taskCommentResource.getTaskId()); + taskComment.setId(taskCommentResource.getTaskCommentId()); + + BeanUtils.copyProperties(taskCommentResource, taskComment); + + if (taskCommentResource.getCreated() != null) { + taskComment.setCreated(Instant.parse(taskCommentResource.getCreated())); + } + if (taskCommentResource.getModified() != null) { + taskComment.setModified(Instant.parse(taskCommentResource.getModified())); + } + + return taskComment; + } +} diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/RestHelper.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/RestHelper.java index b651463ec..143c908a5 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/RestHelper.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/RestHelper.java @@ -64,6 +64,13 @@ public class RestHelper { return headers; } + public HttpHeaders getHeadersUser_1_1() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Basic dXNlcl8xXzE6dXNlcl8xXzE="); + headers.add("Content-Type", "application/json"); + return headers; + } + /** * Return a REST template which is capable of dealing with responses in HAL format. * diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskCommentControllerRestDocumentation.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskCommentControllerRestDocumentation.java new file mode 100644 index 000000000..f1668a24d --- /dev/null +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskCommentControllerRestDocumentation.java @@ -0,0 +1,202 @@ +package pro.taskana.doc.api; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; +import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; +import static org.springframework.restdocs.payload.PayloadDocumentation.requestFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.responseFields; +import static org.springframework.restdocs.payload.PayloadDocumentation.subsectionWithPath; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.restdocs.mockmvc.MockMvcRestDocumentation; +import org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders; +import org.springframework.restdocs.payload.FieldDescriptor; +import org.springframework.test.web.servlet.MvcResult; +import org.springframework.test.web.servlet.result.MockMvcResultMatchers; + +import pro.taskana.rest.Mapping; + +public class TaskCommentControllerRestDocumentation extends BaseRestDocumentation { + + private HashMap taskCommentFieldDescriptionsMap = new HashMap(); + + private FieldDescriptor[] allTaskCommentsFieldDescriptors; + private FieldDescriptor[] taskCommentFieldDescriptors; + private FieldDescriptor[] createTaskCommentFieldDescriptors; + + @BeforeEach + void setUp() { + + taskCommentFieldDescriptionsMap.put("taskCommentId", "Unique ID"); + taskCommentFieldDescriptionsMap.put( + "taskId", "Task ID. Can identify the task to which the comment belongs"); + taskCommentFieldDescriptionsMap.put("textField", "The content of the actual comment"); + taskCommentFieldDescriptionsMap.put("creator", "The creator of the task comment"); + taskCommentFieldDescriptionsMap.put( + "created", "The creation timestamp of the task comment in the system."); + taskCommentFieldDescriptionsMap.put( + "modified", "Timestamp of the last modification of the task comment"); + + taskCommentFieldDescriptors = + new FieldDescriptor[] { + fieldWithPath("taskCommentId") + .description(taskCommentFieldDescriptionsMap.get("taskCommentId")), + fieldWithPath("taskId").description(taskCommentFieldDescriptionsMap.get("taskId")), + fieldWithPath("textField").description(taskCommentFieldDescriptionsMap.get("textField")), + fieldWithPath("creator").description(taskCommentFieldDescriptionsMap.get("creator")), + fieldWithPath("created") + .description(taskCommentFieldDescriptionsMap.get("created")) + .type("String"), + fieldWithPath("modified") + .description(taskCommentFieldDescriptionsMap.get("modified")) + .type("String"), + fieldWithPath("_links").ignored(), + fieldWithPath("_links.self").ignored(), + fieldWithPath("_links.self.href").ignored(), + fieldWithPath("_links.self.templated").ignored() + }; + + createTaskCommentFieldDescriptors = + new FieldDescriptor[] { + fieldWithPath("taskId").description(taskCommentFieldDescriptionsMap.get("taskId")), + fieldWithPath("textField").description(taskCommentFieldDescriptionsMap.get("textField")), + fieldWithPath("creator") + .description(taskCommentFieldDescriptionsMap.get("creator")) + .type("String") + .optional(), + fieldWithPath("created") + .description(taskCommentFieldDescriptionsMap.get("created")) + .type("String") + .optional(), + fieldWithPath("modified") + .description(taskCommentFieldDescriptionsMap.get("modified")) + .type("String") + .optional(), + }; + + allTaskCommentsFieldDescriptors = + new FieldDescriptor[] { + subsectionWithPath("task comments") + .description("An Array of task comments") + }; + } + + @Test + void getAllTaskCommentsForSpecificTaskDocTest() throws Exception { + this.mockMvc + .perform( + RestDocumentationRequestBuilders.get( + restHelper.toUrl( + Mapping.URL_TASK_COMMENTS, "TKI:000000000000000000000000000000000000")) + .accept("application/hal+json") + .header("Authorization", "Basic YWRtaW46YWRtaW4=")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentation.document( + "GetAllTaskCommentsForSpecificTaskDocTest", + responseFields(allTaskCommentsFieldDescriptors))); + } + + @Test + void getSpecificTaskCommentDocTest() throws Exception { + this.mockMvc + .perform( + RestDocumentationRequestBuilders.get( + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:100000000000000000000000000000000000", + "TCI:000000000000000000000000000000000000")) + .accept("application/hal+json") + .header("Authorization", "Basic YWRtaW46YWRtaW4=")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentation.document( + "GetSpecificTaskCommentDocTest", responseFields(taskCommentFieldDescriptors))); + } + + @Test + void updateTaskCommentDocTest() throws Exception { + URL url = + new URL( + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:000000000000000000000000000000000000", + "TCI:000000000000000000000000000000000000")); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("Authorization", "Basic YWRtaW46YWRtaW4="); + assertEquals(200, con.getResponseCode()); + + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream(), UTF_8)); + String inputLine; + StringBuilder content = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + content.append(inputLine); + } + in.close(); + con.disconnect(); + String originalTaskComment = content.toString(); + String modifiedTaskComment = + originalTaskComment.replace("some text in textfield", "updated text in textfield"); + + this.mockMvc + .perform( + RestDocumentationRequestBuilders.put( + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:000000000000000000000000000000000000", + "TCI:000000000000000000000000000000000000")) + .header("Authorization", "Basic YWRtaW46YWRtaW4=") + .contentType("application/json") + .content(modifiedTaskComment)) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentation.document( + "UpdateTaskCommentDocTest", + requestFields(taskCommentFieldDescriptors), + responseFields(taskCommentFieldDescriptors))); + } + + @Test + void createAndDeleteTaskCommentDocTest() throws Exception { + + MvcResult result = + this.mockMvc + .perform( + RestDocumentationRequestBuilders.post( + restHelper.toUrl( + Mapping.URL_TASK_COMMENTS, "TKI:000000000000000000000000000000000000")) + .contentType("application/hal+json") + .content( + "{ \"taskId\" : \"TKI:000000000000000000000000000000000000\",\n" + + " \"textField\" : \"some text in textfield\"} ") + .header("Authorization", "Basic YWRtaW46YWRtaW4=")) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andDo( + MockMvcRestDocumentation.document( + "CreateTaskCommentDocTest", + requestFields(createTaskCommentFieldDescriptors), + responseFields(taskCommentFieldDescriptors))) + .andReturn(); + + String content = result.getResponse().getContentAsString(); + String newId = content.substring(content.indexOf("TCI:"), content.indexOf("TCI:") + 40); + + this.mockMvc + .perform( + RestDocumentationRequestBuilders.delete( + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:000000000000000000000000000000000000", + newId)) + .header("Authorization", "Basic YWRtaW46YWRtaW4=")) // admin + .andExpect(MockMvcResultMatchers.status().isNoContent()) + .andDo(MockMvcRestDocumentation.document("DeleteTaskCommentDocTest")); + } +} diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskCommentControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskCommentControllerIntTest.java new file mode 100644 index 000000000..8f2889a47 --- /dev/null +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskCommentControllerIntTest.java @@ -0,0 +1,182 @@ +package pro.taskana.rest; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; + +import pro.taskana.RestHelper; +import pro.taskana.TaskanaSpringBootTest; +import pro.taskana.rest.resource.TaskCommentListResource; +import pro.taskana.rest.resource.TaskCommentResource; + +/** Test TaskCommentController. */ +@TaskanaSpringBootTest +class TaskCommentControllerIntTest { + + private static RestTemplate template; + + @Value("${taskana.schemaName:TASKANA}") + public String schemaName; + + @Autowired RestHelper restHelper; + + @BeforeAll + static void init() { + template = RestHelper.TEMPLATE; + } + + @Test + void testGetNonExistentCommentShouldFail() { + + String urlToNonExistingTaskComment = + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:000000000000000000000000000000000000", + "Non existing task comment Id"); + + assertThatThrownBy( + () -> + template.exchange( + urlToNonExistingTaskComment, + HttpMethod.GET, + new HttpEntity(restHelper.getHeadersAdmin()), + ParameterizedTypeReference.forType(TaskCommentResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.BAD_REQUEST); + } + + @Test + void testGetCommentsForNonExistingTaskShouldFail() { + + String urlToNonExistingTask = restHelper.toUrl(Mapping.URL_TASK_COMMENTS, "nonExistingTaskId"); + + assertThatThrownBy( + () -> + template.exchange( + urlToNonExistingTask, + HttpMethod.GET, + new HttpEntity(restHelper.getHeadersAdmin()), + ParameterizedTypeReference.forType(TaskCommentListResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); + } + + @Disabled("Disabled until Authorization check is up!") + @Test + void testGetTaskCommentsOfNotVisibleTaskShouldFail() { + + String urlToNotVisibleTask = + restHelper.toUrl(Mapping.URL_TASK_COMMENTS, "TKI:000000000000000000000000000000000004"); + + ResponseEntity response = + template.exchange( + urlToNotVisibleTask, + HttpMethod.GET, + new HttpEntity(restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentListResource.class)); + } + + @Disabled("Disabled until Authorization check is up!") + @Test + void testGetTaskCommentOfNotVisibleTaskShouldFail() { + + String urlToNotVisibleTask = + restHelper.toUrl( + Mapping.URL_TASK_COMMENT, + "TKI:000000000000000000000000000000000004", + "TCI:000000000000000000000000000000000013"); + + ResponseEntity response = + template.exchange( + urlToNotVisibleTask, + HttpMethod.GET, + new HttpEntity(restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentResource.class)); + } + + @Disabled("Disabled until Authorization check is up!") + @Test + void testCreateTaskCommentForNotVisibleTaskShouldFail() { + + TaskCommentResource taskCommentResourceToCreate = new TaskCommentResource(); + taskCommentResourceToCreate.setTaskId("TKI:000000000000000000000000000000000004"); + taskCommentResourceToCreate.setTextField("newly created task comment"); + + assertThatThrownBy( + () -> + template.exchange( + restHelper.toUrl( + Mapping.URL_TASK_COMMENTS, "TKI:000000000000000000000000000000000004"), + HttpMethod.POST, + new HttpEntity<>(taskCommentResourceToCreate, restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.FORBIDDEN); + } + + @Test + void testCreateTaskCommentForNotExistingTaskShouldFail() { + + TaskCommentResource taskCommentResourceToCreate = new TaskCommentResource(); + taskCommentResourceToCreate.setTaskId("DefinatelyNotExistingId"); + taskCommentResourceToCreate.setTextField("newly created task comment"); + + assertThatThrownBy( + () -> + template.exchange( + restHelper.toUrl(Mapping.URL_TASK_COMMENTS, "NotExistingTaskId"), + HttpMethod.POST, + new HttpEntity<>(taskCommentResourceToCreate, restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); + + TaskCommentResource taskCommentResourceToCreate2 = new TaskCommentResource(); + taskCommentResourceToCreate.setTaskId(null); + taskCommentResourceToCreate.setTextField("newly created task comment"); + + assertThatThrownBy( + () -> + template.exchange( + restHelper.toUrl(Mapping.URL_TASK_COMMENTS, "NotExistingTaskId"), + HttpMethod.POST, + new HttpEntity<>(taskCommentResourceToCreate2, restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); + + TaskCommentResource taskCommentResourceToCreate3 = new TaskCommentResource(); + taskCommentResourceToCreate.setTaskId(""); + taskCommentResourceToCreate.setTextField("newly created task comment"); + + assertThatThrownBy( + () -> + template.exchange( + restHelper.toUrl(Mapping.URL_TASK_COMMENTS, "NotExistingTaskId"), + HttpMethod.POST, + new HttpEntity<>(taskCommentResourceToCreate3, restHelper.getHeadersUser_1_1()), + ParameterizedTypeReference.forType(TaskCommentResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.NOT_FOUND); + } + + @Test + void testUpdateTaskCommentWithConcurrentModificationShouldFail() {} + + @Test + void testUpdateTaskCommentWithNoAuthorizationShouldFail() {} + + @Test + void testUpdateTaskCommentOfNotExistingTaskShouldFail() {} +} diff --git a/rest/taskana-rest-spring/src/test/resources/asciidoc/rest-api.adoc b/rest/taskana-rest-spring/src/test/resources/asciidoc/rest-api.adoc index 80bcdb9bb..2e9fe465b 100644 --- a/rest/taskana-rest-spring/src/test/resources/asciidoc/rest-api.adoc +++ b/rest/taskana-rest-spring/src/test/resources/asciidoc/rest-api.adoc @@ -293,6 +293,97 @@ include::{snippets}/TransferTaskDocTest/http-response.adoc[] The response-body is essentially the same as for getting a single task. + Therefore for the response fields you can refer to the <>. +== TaskComment-Resource + +=== Get a list of all task comments for a specific task + +A `GET` request is used to retrieve the task comments. + +==== Example Request + +include::{snippets}/GetAllTaskCommentsForSpecificTaskDocTest/http-request.adoc[] + +==== Example Response + +include::{snippets}/GetAllTaskCommentsForSpecificTaskDocTest/http-response.adoc[] + +==== Response Structure + +include::{snippets}/GetAllTaskCommentsForSpecificTaskDocTest/response-fields.adoc[] + + +=== Get a specific task comment + +A `GET` request is used to retrieve a task comment. + +==== Example Request + +include::{snippets}/GetSpecificTaskCommentDocTest/http-request.adoc[] + +==== Example Response + +include::{snippets}/GetSpecificTaskCommentDocTest/http-response.adoc[] + +==== Response Structure + +include::{snippets}/GetSpecificTaskCommentDocTest/response-fields.adoc[] + +=== Update a task comment + +A `PUT` request is used to update a task comment. + +==== Example Request + +include::{snippets}/UpdateTaskCommentDocTest/http-request.adoc[] + +==== Request Structure + +include::{snippets}/UpdateTaskCommentDocTest/request-fields.adoc[] + +==== Example Response + +include::{snippets}/UpdateTaskCommentDocTest/http-response.adoc[] + +==== Response Structure + +include::{snippets}/UpdateTaskCommentDocTest/response-fields.adoc[] + +=== Create a new task comment + +A `POST` request is used to create a new task comment. + +==== Example Request + +This minimal example shows only the required fields to create a new task comment. The <> shows all possible fields for creating a task comment. + +include::{snippets}/CreateTaskCommentDocTest/http-request.adoc[] + +[[create-taskcomment--request-structure, request structure]] +==== Request Structure + +include::{snippets}/CreateTaskCommentDocTest/request-fields.adoc[] + +==== Example Response + +include::{snippets}/CreateTaskCommentDocTest/http-response.adoc[] + +==== Response Structure + +The response-body is essentially the same as for getting a specific task comment. + +Therefore for the response fields you can refer to the <>. + +=== Delete a task comment + +A `DELETE` request is used to delete a task comment. + +==== Example request + +include::{snippets}/DeleteTaskCommentDocTest/http-request.adoc[] + +==== Example response + +include::{snippets}/DeleteTaskCommentDocTest/http-response.adoc[] + == Classifications-Resource This resource provides the entry point with classifications.