|
15 | 15 | import static org.hamcrest.Matchers.not; |
16 | 16 | import static org.junit.Assert.assertFalse; |
17 | 17 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
| 18 | +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; |
18 | 19 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
19 | 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; |
20 | 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
21 | 22 |
|
| 23 | +import java.util.ArrayList; |
| 24 | +import java.util.HashMap; |
| 25 | +import java.util.HashSet; |
22 | 26 | import java.util.List; |
| 27 | +import java.util.Map; |
| 28 | +import java.util.Set; |
23 | 29 | import java.util.UUID; |
24 | 30 | import java.util.concurrent.atomic.AtomicReference; |
25 | 31 |
|
26 | 32 | import org.apache.commons.lang3.StringUtils; |
| 33 | +import org.dspace.app.rest.model.patch.AddOperation; |
| 34 | +import org.dspace.app.rest.model.patch.Operation; |
| 35 | +import org.dspace.app.rest.model.patch.ReplaceOperation; |
| 36 | +import org.dspace.app.rest.repository.ClarinLicenseRestRepository; |
27 | 37 | import org.dspace.app.rest.test.AbstractControllerIntegrationTest; |
| 38 | +import org.dspace.builder.ClaimedTaskBuilder; |
| 39 | +import org.dspace.builder.ClarinLicenseBuilder; |
| 40 | +import org.dspace.builder.ClarinLicenseLabelBuilder; |
28 | 41 | import org.dspace.builder.CollectionBuilder; |
29 | 42 | import org.dspace.builder.CommunityBuilder; |
30 | 43 | import org.dspace.builder.EPersonBuilder; |
|
35 | 48 | import org.dspace.content.Item; |
36 | 49 | import org.dspace.content.MetadataValue; |
37 | 50 | import org.dspace.content.WorkspaceItem; |
| 51 | +import org.dspace.content.clarin.ClarinLicense; |
| 52 | +import org.dspace.content.clarin.ClarinLicenseLabel; |
38 | 53 | import org.dspace.content.service.ItemService; |
39 | 54 | import org.dspace.content.service.WorkspaceItemService; |
| 55 | +import org.dspace.content.service.clarin.ClarinLicenseLabelService; |
| 56 | +import org.dspace.content.service.clarin.ClarinLicenseService; |
40 | 57 | import org.dspace.eperson.EPerson; |
41 | 58 | import org.dspace.license.service.CreativeCommonsService; |
42 | 59 | import org.dspace.services.ConfigurationService; |
43 | 60 | import org.dspace.xmlworkflow.factory.XmlWorkflowFactory; |
| 61 | +import org.dspace.xmlworkflow.storedcomponents.ClaimedTask; |
| 62 | +import org.dspace.xmlworkflow.storedcomponents.XmlWorkflowItem; |
44 | 63 | import org.dspace.xmlworkflow.storedcomponents.service.CollectionRoleService; |
45 | 64 | import org.dspace.xmlworkflow.storedcomponents.service.XmlWorkflowItemService; |
46 | 65 | import org.hamcrest.Matchers; |
@@ -78,6 +97,11 @@ public class ClarinWorkflowItemRestRepositoryIT extends AbstractControllerIntegr |
78 | 97 | @Autowired |
79 | 98 | private ItemService itemService; |
80 | 99 |
|
| 100 | + @Autowired |
| 101 | + private ClarinLicenseService clarinLicenseService; |
| 102 | + @Autowired |
| 103 | + private ClarinLicenseLabelService clarinLicenseLabelService; |
| 104 | + |
81 | 105 | Item item; |
82 | 106 |
|
83 | 107 | @Before |
@@ -364,4 +388,143 @@ public void shouldCreateItemWithCustomTypeBindField() throws Exception { |
364 | 388 | assertFalse(mvList.isEmpty()); |
365 | 389 | assertThat(mvList.get(0).getValue(), is(CITATION_VALUE)); |
366 | 390 | } |
| 391 | + |
| 392 | + @Test |
| 393 | + public void patchUpdateClarinLicense() throws Exception { |
| 394 | + context.turnOffAuthorisationSystem(); |
| 395 | + |
| 396 | + // create Clarin License Label |
| 397 | + ClarinLicenseLabel clarinLicenseLabel = ClarinLicenseLabelBuilder.createClarinLicenseLabel(context).build(); |
| 398 | + clarinLicenseLabel.setLabel("CC"); |
| 399 | + clarinLicenseLabel.setExtended(false); |
| 400 | + clarinLicenseLabel.setTitle("CLL Title1"); |
| 401 | + clarinLicenseLabelService.update(context, clarinLicenseLabel); |
| 402 | + |
| 403 | + // create Clarin License |
| 404 | + ClarinLicense clarinLicense = ClarinLicenseBuilder.createClarinLicense(context).build(); |
| 405 | + clarinLicense.setName("CL Name"); |
| 406 | + clarinLicense.setConfirmation(ClarinLicense.Confirmation.NOT_REQUIRED); |
| 407 | + clarinLicense.setDefinition("CL Definition"); |
| 408 | + clarinLicense.setRequiredInfo("CL Req"); |
| 409 | + // add clarinLicenseLabel to clarinLicense |
| 410 | + Set<ClarinLicenseLabel> clarinLicenseLabels = new HashSet<>(); |
| 411 | + clarinLicenseLabels.add(clarinLicenseLabel); |
| 412 | + clarinLicense.setLicenseLabels(clarinLicenseLabels); |
| 413 | + clarinLicenseService.update(context, clarinLicense); |
| 414 | + |
| 415 | + // community with one collection. |
| 416 | + parentCommunity = CommunityBuilder.createCommunity(context) |
| 417 | + .withName("Parent Community") |
| 418 | + .build(); |
| 419 | + Collection col = CollectionBuilder.createCollection(context, parentCommunity).withName("Collection 1") |
| 420 | + .withWorkflowGroup("editor", eperson).build(); |
| 421 | + |
| 422 | + // create a normal user to use as submitter |
| 423 | + EPerson submitter = EPersonBuilder.createEPerson(context) |
| 424 | + .withEmail("submitter@example.com") |
| 425 | + .withPassword("dspace") |
| 426 | + .build(); |
| 427 | + |
| 428 | + // claimed task with workflow item in edit step |
| 429 | + ClaimedTask claimedTask = ClaimedTaskBuilder.createClaimedTask(context, col, eperson) |
| 430 | + .withTitle("Workflow Item") |
| 431 | + .withIssueDate("2026-05-18") |
| 432 | + .withSubject("Extra Entry") |
| 433 | + .grantLicense() |
| 434 | + .build(); |
| 435 | + claimedTask.setStepID("editstep"); |
| 436 | + claimedTask.setActionID("editaction"); |
| 437 | + XmlWorkflowItem wfItem = claimedTask.getWorkflowItem(); |
| 438 | + |
| 439 | + context.restoreAuthSystemState(); |
| 440 | + |
| 441 | + // prepare a patch targeting the clarin license resource path |
| 442 | + List<Operation> ops = new ArrayList<>(); |
| 443 | + ops.add(new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, "CL Name")); |
| 444 | + |
| 445 | + String submitterToken = getAuthToken(submitter.getEmail(), "dspace"); |
| 446 | + |
| 447 | + // The submitter shouldn't be allowed to patch clarin license |
| 448 | + // because the workflow item was claimed by the other user (eperson), |
| 449 | + // and the submitter doesn't have permissions to edit it, |
| 450 | + // so the patch request should be rejected with error 403(Forbidden) |
| 451 | + getClient(submitterToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 452 | + .content(getPatchContent(ops)) |
| 453 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 454 | + .andExpect(status().isForbidden()); |
| 455 | + |
| 456 | + String editorToken = getAuthToken(eperson.getEmail(), password); |
| 457 | + |
| 458 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, "Wrong CL")); |
| 459 | + |
| 460 | + // The wrong clarin license name value should be rejected with 404 Not Found |
| 461 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 462 | + .content(getPatchContent(ops)) |
| 463 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 464 | + .andExpect(status().isNotFound()); |
| 465 | + |
| 466 | + // The valid clarin license name can be in the form of a simple string or |
| 467 | + // in the form of a map with "value" key, but it should be accepted in both cases |
| 468 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, "CL Name")); |
| 469 | + |
| 470 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 471 | + .content(getPatchContent(ops)) |
| 472 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 473 | + .andExpect(status().isOk()); |
| 474 | + |
| 475 | + XmlWorkflowItem updatedWfItem = xmlWorkflowItemService.find(context, wfItem.getID()); |
| 476 | + assertThat(itemService.getMetadataFirstValue(updatedWfItem.getItem(), "dc", "rights", null, Item.ANY), |
| 477 | + is("CL Name")); |
| 478 | + |
| 479 | + Map<String, String> wrappedValue = new HashMap<String, String>(); |
| 480 | + wrappedValue.put("value", "CL Name"); |
| 481 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, |
| 482 | + wrappedValue)); |
| 483 | + |
| 484 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 485 | + .content(getPatchContent(ops)) |
| 486 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 487 | + .andExpect(status().isOk()); |
| 488 | + |
| 489 | + // The wrapped value should contain the "value" key, otherwise it is invalid |
| 490 | + Map<String, String> invalidWrappedValue1 = new HashMap<String, String>(); |
| 491 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, |
| 492 | + invalidWrappedValue1)); |
| 493 | + |
| 494 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 495 | + .content(getPatchContent(ops)) |
| 496 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 497 | + .andExpect(status().isBadRequest()); |
| 498 | + |
| 499 | + // The wrapped value should be in a map, not in a list |
| 500 | + List<String> invalidWrappedValue2 = new ArrayList<>(); |
| 501 | + invalidWrappedValue2.add("CL Name"); |
| 502 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, |
| 503 | + invalidWrappedValue2)); |
| 504 | + |
| 505 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 506 | + .content(getPatchContent(ops)) |
| 507 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 508 | + .andExpect(status().isBadRequest()); |
| 509 | + |
| 510 | + // The only accepted operation for clarin license resource is "replace", |
| 511 | + // "add" operation should be rejected with 400 Bad Request even with the valid value |
| 512 | + ops.set(0, new AddOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, |
| 513 | + "CL Name")); |
| 514 | + |
| 515 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + wfItem.getID()) |
| 516 | + .content(getPatchContent(ops)) |
| 517 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 518 | + .andExpect(status().isBadRequest()); |
| 519 | + |
| 520 | + // The reworked claim guard adds a 'source == null' check: a PATCH on a |
| 521 | + // nonexistent workflow item id must return 404 Not Found (pre-fix: NPE) |
| 522 | + ops.set(0, new ReplaceOperation("/" + ClarinLicenseRestRepository.OPERATION_PATH_LICENSE_RESOURCE, |
| 523 | + "CL Name")); |
| 524 | + |
| 525 | + getClient(editorToken).perform(patch("/api/workflow/workflowitems/" + 999999) |
| 526 | + .content(getPatchContent(ops)) |
| 527 | + .contentType(jakarta.ws.rs.core.MediaType.APPLICATION_JSON_PATCH_JSON)) |
| 528 | + .andExpect(status().isNotFound()); |
| 529 | + } |
367 | 530 | } |
0 commit comments