diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java index 8b826dc18..5b76093dd 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/rest/TaskController.java @@ -155,6 +155,26 @@ public class TaskController extends AbstractPagingController { return result; } + @DeleteMapping(path = Mapping.URL_TASKS_ID_CLAIM) + @Transactional(rollbackFor = Exception.class) + public ResponseEntity cancelClaimTask( + @PathVariable String taskId) + throws TaskNotFoundException, InvalidStateException, InvalidOwnerException, + NotAuthorizedException { + + LOGGER.debug("Entry to cancelClaimTask(taskId= {}", taskId); + + taskService.cancelClaim(taskId); + Task updatedTask = taskService.getTask(taskId); + + ResponseEntity result = + ResponseEntity.ok(taskResourceAssembler.toResource(updatedTask)); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Exit from cancelClaimTask(), returning {}", result); + } + return result; + } + @PostMapping(path = Mapping.URL_TASKS_ID_COMPLETE) @Transactional(rollbackFor = Exception.class) public ResponseEntity completeTask(@PathVariable String taskId) 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 499fbb385..91d97ee97 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 @@ -57,6 +57,13 @@ public class RestHelper { return headers; } + public HttpHeaders getHeadersUser_1_2() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Basic dXNlcl8xXzI6dXNlcl8xXzI="); + 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/TaskControllerRestDocumentation.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskControllerRestDocumentation.java index 816a689c9..576cff190 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskControllerRestDocumentation.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/doc/api/TaskControllerRestDocumentation.java @@ -614,13 +614,42 @@ class TaskControllerRestDocumentation extends BaseRestDocumentation { .andDo( MockMvcRestDocumentation.document( "ClaimTaskDocTest", responseFields(taskFieldDescriptors))); + } + + @Test + void cancelClaimTaskDocTest() throws Exception { + + MvcResult result = + this.mockMvc + .perform( + RestDocumentationRequestBuilders.post(restHelper.toUrl(Mapping.URL_TASKS)) + .contentType("application/hal+json") + .content( + "{\"classificationSummaryResource\":{\"key\":\"L11010\"}," + + "\"workbasketSummaryResource\":" + + "{\"workbasketId\":\"WBI:100000000000000000000000000000000004\"}," + + "\"primaryObjRef\":{\"company\":\"MyCompany1\"," + + "\"system\":\"MySystem1\",\"systemInstance\":\"MyInstance1\"," + + "\"type\":\"MyType1\",\"value\":\"00000001\"}}") + .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x")) + .andExpect(MockMvcResultMatchers.status().isCreated()) + .andDo(MockMvcRestDocumentation.document("temp")) + .andReturn(); + + String content = result.getResponse().getContentAsString(); + String newId = content.substring(content.indexOf("TKI:"), content.indexOf("TKI:") + 40); this.mockMvc .perform( - RestDocumentationRequestBuilders.delete(restHelper.toUrl(Mapping.URL_TASKS_ID, newId)) - .header("Authorization", "Basic YWRtaW46YWRtaW4=")) // admin - .andExpect(MockMvcResultMatchers.status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("DeleteTaskDocTest")); + RestDocumentationRequestBuilders.delete( + restHelper.toUrl(Mapping.URL_TASKS_ID_CLAIM, newId)) + .accept("application/hal+json") + .header("Authorization", "Basic dGVhbWxlYWRfMTp0ZWFtbGVhZF8x") + .content("{}")) + .andExpect(MockMvcResultMatchers.status().isOk()) + .andDo( + MockMvcRestDocumentation.document( + "CancelClaimTaskDocTest", responseFields(taskFieldDescriptors))); } @Test @@ -656,13 +685,6 @@ class TaskControllerRestDocumentation extends BaseRestDocumentation { .andDo( MockMvcRestDocumentation.document( "CompleteTaskDocTest", responseFields(taskFieldDescriptors))); - - this.mockMvc - .perform( - RestDocumentationRequestBuilders.delete(restHelper.toUrl(Mapping.URL_TASKS_ID, newId)) - .header("Authorization", "Basic YWRtaW46YWRtaW4=")) // admin - .andExpect(MockMvcResultMatchers.status().isNoContent()) - .andDo(MockMvcRestDocumentation.document("DeleteTaskDocTest")); } @Test @@ -701,11 +723,5 @@ class TaskControllerRestDocumentation extends BaseRestDocumentation { .andDo( MockMvcRestDocumentation.document( "TransferTaskDocTest", responseFields(taskFieldDescriptors))); - - this.mockMvc - .perform( - RestDocumentationRequestBuilders.delete(restHelper.toUrl(Mapping.URL_TASKS_ID, newId)) - .header("Authorization", "Basic YWRtaW46YWRtaW4=")) // admin - .andExpect(MockMvcResultMatchers.status().isNoContent()); } } diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskControllerIntTest.java index 48d7aa663..9ef3b196f 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/rest/TaskControllerIntTest.java @@ -639,6 +639,76 @@ class TaskControllerIntTest { con.disconnect(); } + @Test + void testCancelClaimTask() { + + final String claimed_task_id = "TKI:000000000000000000000000000000000027"; + final String user_id_of_claimed_task = "user_1_2"; + + // retrieve task from Rest Api + ResponseEntity getTaskResponse = + template.exchange( + restHelper.toUrl(Mapping.URL_TASKS_ID, claimed_task_id), + HttpMethod.GET, + new HttpEntity<>(restHelper.getHeadersUser_1_2()), + ParameterizedTypeReference.forType(TaskResource.class)); + + assertThat(getTaskResponse.getBody()).isNotNull(); + + TaskResource claimedTaskResource = getTaskResponse.getBody(); + assertThat(claimedTaskResource.getState()).isEqualTo(TaskState.CLAIMED); + assertThat(claimedTaskResource.getOwner()).isEqualTo(user_id_of_claimed_task); + + //cancel claim + ResponseEntity cancelClaimResponse = + template.exchange( + restHelper.toUrl(Mapping.URL_TASKS_ID_CLAIM, claimed_task_id), + HttpMethod.DELETE, + new HttpEntity<>(restHelper.getHeadersUser_1_2()), + ParameterizedTypeReference.forType(TaskResource.class)); + + assertThat(cancelClaimResponse.getBody()).isNotNull(); + assertThat(cancelClaimResponse.getStatusCode().is2xxSuccessful()); + + TaskResource cancelClaimedtaskResource = cancelClaimResponse.getBody(); + assertThat(cancelClaimedtaskResource.getOwner()).isNull(); + assertThat(cancelClaimedtaskResource.getClaimed()).isNull(); + assertThat(cancelClaimedtaskResource.getState()).isEqualTo(TaskState.READY); + } + + @Test + void testCancelClaimOfClaimedTaskByAnotherUserShouldThrowException() { + + final String claimed_task_id = "TKI:000000000000000000000000000000000026"; + final String user_id_of_claimed_task = "user_1_1"; + + // retrieve task from Rest Api + ResponseEntity responseGet = + template.exchange( + restHelper.toUrl(Mapping.URL_TASKS_ID, claimed_task_id), + HttpMethod.GET, + new HttpEntity<>(restHelper.getHeadersUser_1_2()), + ParameterizedTypeReference.forType(TaskResource.class)); + + assertThat(responseGet.getBody()).isNotNull(); + TaskResource theTaskResource = responseGet.getBody(); + assertThat(theTaskResource.getState()).isEqualTo(TaskState.CLAIMED); + assertThat(theTaskResource.getOwner()).isEqualTo(user_id_of_claimed_task); + + //try to cancel claim + assertThatThrownBy( + () -> + template.exchange( + restHelper.toUrl(Mapping.URL_TASKS_ID_CLAIM, claimed_task_id), + HttpMethod.DELETE, + new HttpEntity<>(restHelper.getHeadersUser_1_2()), + ParameterizedTypeReference.forType(TaskResource.class))) + .extracting(ex -> ((HttpClientErrorException) ex).getStatusCode()) + .isEqualTo(HttpStatus.CONFLICT); + } + + + @Test void testUpdateTaskOwnerOfReadyTaskSucceeds() { // setup @@ -650,7 +720,7 @@ class TaskControllerIntTest { template.exchange( taskUrlString, HttpMethod.GET, - new HttpEntity<>(getHeadersForUser_1_2()), + new HttpEntity<>(restHelper.getHeadersUser_1_2()), ParameterizedTypeReference.forType(TaskResource.class)); assertThat(responseGet.getBody()).isNotNull(); @@ -666,7 +736,7 @@ class TaskControllerIntTest { template.exchange( taskUrlString, HttpMethod.PUT, - new HttpEntity<>(theTaskResource, getHeadersForUser_1_2()), + new HttpEntity<>(theTaskResource, restHelper.getHeadersUser_1_2()), ParameterizedTypeReference.forType(TaskResource.class)); assertThat(responseUpdate.getBody()).isNotNull(); @@ -686,7 +756,7 @@ class TaskControllerIntTest { template.exchange( taskUrlString, HttpMethod.GET, - new HttpEntity<>(getHeadersForUser_1_2()), + new HttpEntity<>(restHelper.getHeadersUser_1_2()), ParameterizedTypeReference.forType(TaskResource.class)); assertThat(responseGet.getBody()).isNotNull(); @@ -704,19 +774,12 @@ class TaskControllerIntTest { template.exchange( taskUrlString, HttpMethod.PUT, - new HttpEntity<>(theTaskResource, getHeadersForUser_1_2()), + new HttpEntity<>(theTaskResource, restHelper.getHeadersUser_1_2()), ParameterizedTypeReference.forType(TaskResource.class))) .isInstanceOf(HttpClientErrorException.class) .hasMessageContaining("409"); } - private HttpHeaders getHeadersForUser_1_2() { - HttpHeaders headers = new HttpHeaders(); - headers.add("Authorization", "Basic dXNlcl8xXzI6dXNlcl8xXzI="); - headers.add("Content-Type", "application/json"); - return headers; - } - private TaskResource getTaskResourceSample() { ClassificationSummaryResource classificationResource = new ClassificationSummaryResource(); classificationResource.setKey("L11010"); 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 d89bbfb69..cc776fbd8 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 @@ -195,6 +195,26 @@ include::{snippets}/ClaimTaskDocTest/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 <>. +=== Cancel Claim a task + +A `DELETE` request is used to cancel claim a task + +==== Example Request + +Note the empty request-body in the example. + +include::{snippets}/CancelClaimTaskDocTest/http-request.adoc[] + +==== Example Response + +include::{snippets}/CancelClaimTaskDocTest/http-response.adoc[] + +==== Response Structure + +The response-body is essentially the same as for getting a single task. + +Therefore for the response fields you can refer to the <>. + + === Complete a task A `POST` request is used to complete a task