Merge pull request #11137 from DSpace/backport-11131-to-dspace-9_x

[Port dspace-9_x] Enhance SWORDv1 Integration Tests & fix WRITE Permissions error for submitters
This commit is contained in:
Tim Donohue
2025-08-06 13:33:31 -05:00
committed by GitHub
2 changed files with 98 additions and 17 deletions

View File

@@ -498,8 +498,11 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester {
// Finish creating the item. This actually assigns the handle, // Finish creating the item. This actually assigns the handle,
// and will either install item immediately or start a workflow, based on params // and will either install item immediately or start a workflow, based on params
PackageUtils.finishCreateItem(context, wsi, handle, params); PackageUtils.finishCreateItem(context, wsi, handle, params);
} else {
// We should have a workspace item during ingest, so this code is only here for safety.
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
} }
} else if (type == Constants.COLLECTION || type == Constants.COMMUNITY) { } else if (type == Constants.COLLECTION || type == Constants.COMMUNITY) {
// Add logo if one is referenced from manifest // Add logo if one is referenced from manifest
addContainerLogo(context, dso, manifest, pkgFile, params); addContainerLogo(context, dso, manifest, pkgFile, params);
@@ -513,6 +516,9 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester {
// (this allows subclasses to do some final validation / changes as // (this allows subclasses to do some final validation / changes as
// necessary) // necessary)
finishObject(context, dso, params); finishObject(context, dso, params);
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
} else if (type == Constants.SITE) { } else if (type == Constants.SITE) {
// Do nothing by default -- Crosswalks will handle anything necessary to ingest at Site-level // Do nothing by default -- Crosswalks will handle anything necessary to ingest at Site-level
@@ -520,18 +526,15 @@ public abstract class AbstractMETSIngester extends AbstractPackageIngester {
// (this allows subclasses to do some final validation / changes as // (this allows subclasses to do some final validation / changes as
// necessary) // necessary)
finishObject(context, dso, params); finishObject(context, dso, params);
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
} else { } else {
throw new PackageValidationException( throw new PackageValidationException(
"Unknown DSpace Object type in package, type=" "Unknown DSpace Object type in package, type="
+ String.valueOf(type)); + String.valueOf(type));
} }
// -- Step 6 --
// Finish things up!
// Update the object to make sure all changes are committed
PackageUtils.updateDSpaceObject(context, dso);
return dso; return dso;
} }

View File

@@ -10,16 +10,30 @@ package org.dspace.app.sword;
import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.nio.file.Path;
import java.util.List;
import org.dspace.app.rest.test.AbstractWebClientIntegrationTest; import org.dspace.app.rest.test.AbstractWebClientIntegrationTest;
import org.dspace.builder.CollectionBuilder;
import org.dspace.builder.CommunityBuilder;
import org.dspace.content.Collection;
import org.dspace.services.ConfigurationService; import org.dspace.services.ConfigurationService;
import org.hamcrest.MatcherAssert;
import org.junit.Assume; import org.junit.Assume;
import org.junit.Before; import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
@@ -45,6 +59,9 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest {
private final String DEPOSIT_PATH = "/sword/deposit"; private final String DEPOSIT_PATH = "/sword/deposit";
private final String MEDIA_LINK_PATH = "/sword/media-link"; private final String MEDIA_LINK_PATH = "/sword/media-link";
// ATOM Content type returned by SWORDv1
private final String ATOM_CONTENT_TYPE = "application/atom+xml;charset=UTF-8";
@Before @Before
public void onlyRunIfConfigExists() { public void onlyRunIfConfigExists() {
// These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD) // These integration tests REQUIRE that SWORDWebConfig is found/available (as this class deploys SWORD)
@@ -93,10 +110,76 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest {
} }
@Test @Test
@Ignore
public void depositTest() throws Exception { public void depositTest() throws Exception {
// TODO: Actually test a full deposit via SWORD. context.turnOffAuthorisationSystem();
// Currently, we are just ensuring the /deposit endpoint exists (see above) and isn't throwing a 404 // Create a top level community and one Collection
parentCommunity = CommunityBuilder.createCommunity(context)
.withName("Parent Community")
.build();
// Make sure our Collection allows the "eperson" user to submit into it
Collection collection = CollectionBuilder.createCollection(context, parentCommunity)
.withName("Test SWORDv1 Collection")
.withSubmitterGroup(eperson)
.build();
// Above changes MUST be committed to the database for SWORDv2 to see them.
context.commit();
context.restoreAuthSystemState();
// Specify zip file
// NOTE: We are using the same "example.zip" as SWORDv2IT because that same ZIP is valid for both v1 and v2
FileSystemResource zipFile = new FileSystemResource(Path.of("src", "test", "resources", "org",
"dspace", "app", "sword2", "example.zip"));
// Add required headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("application/zip"));
headers.setContentDisposition(ContentDisposition.attachment().filename("example.zip").build());
headers.set("X-Packaging", "http://purl.org/net/sword-types/METSDSpaceSIP");
headers.setAccept(List.of(MediaType.APPLICATION_ATOM_XML));
//----
// STEP 1: Verify upload/submit via SWORDv1 works
//----
// Send POST to upload Zip file via SWORD
ResponseEntity<String> response = postResponseAsString(DEPOSIT_PATH + "/" + collection.getHandle(),
eperson.getEmail(), password,
new HttpEntity<>(zipFile.getContentAsByteArray(),
headers));
// Expect a 201 CREATED response with ATOM content returned
assertEquals(HttpStatus.CREATED, response.getStatusCode());
assertEquals(ATOM_CONTENT_TYPE, response.getHeaders().getContentType().toString());
// MUST return a "Location" header which is the "/sword/media-link/*" URI of the zip file bitstream within
// the created item (e.g. /sword/media-link/[handle-prefix]/[handle-suffix]/bitstream/[uuid])
assertNotNull(response.getHeaders().getLocation());
String mediaLink = response.getHeaders().getLocation().toString();
// Body should include the SWORD version in generator tag
MatcherAssert.assertThat(response.getBody(),
containsString("<atom:generator uri=\"http://www.dspace.org/ns/sword/1.3.1\"" +
" version=\"1.3\"/>"));
// Verify Item title also is returned in the body
MatcherAssert.assertThat(response.getBody(), containsString("Attempts to detect retrotransposition"));
//----
// STEP 2: Verify /media-link access works
//----
// Media-Link URI should work when requested by the EPerson who did the deposit
HttpHeaders authHeaders = new HttpHeaders();
authHeaders.setBasicAuth(eperson.getEmail(), password);
RequestEntity request = RequestEntity.get(mediaLink)
.accept(MediaType.valueOf("application/atom+xml"))
.headers(authHeaders)
.build();
response = responseAsString(request);
// Expect a 200 response with ATOM feed content returned
assertEquals(HttpStatus.OK, response.getStatusCode());
assertEquals(ATOM_CONTENT_TYPE, response.getHeaders().getContentType().toString());
// Body should include a link to the zip bitstream in the newly created Item
// This just verifies "example.zip" exists in the body.
MatcherAssert.assertThat(response.getBody(), containsString("example.zip"));
} }
@Test @Test
@@ -105,13 +188,8 @@ public class Swordv1IT extends AbstractWebClientIntegrationTest {
ResponseEntity<String> response = getResponseAsString(MEDIA_LINK_PATH); ResponseEntity<String> response = getResponseAsString(MEDIA_LINK_PATH);
// Expect a 401 response code // Expect a 401 response code
assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED)); assertThat(response.getStatusCode(), equalTo(HttpStatus.UNAUTHORIZED));
}
@Test //NOTE: An authorized /media-link test is performed in depositTest() above.
@Ignore
public void mediaLinkTest() throws Exception {
// TODO: Actually test a /media-link request.
// Currently, we are just ensuring the /media-link endpoint exists (see above) and isn't throwing a 404
} }
} }