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.