From 876d0e9a8276357c8547c25fa34597a5bedfa3d2 Mon Sep 17 00:00:00 2001 From: Tom Desair Date: Fri, 6 Oct 2017 16:45:10 +0200 Subject: [PATCH] DS-3484: Initial setup of Spring MVC test + RootRestResourceControllerTest --- LICENSES_THIRD_PARTY | 2 +- dspace-api/pom.xml | 2 +- .../contiperf/junit/ContiPerfRuleExt.java | 118 ++++++++++++ .../org/dspace/AbstractIntegrationTest.java | 3 +- dspace-oai/pom.xml | 2 +- dspace-spring-rest/pom.xml | 168 +++++++++++++++++- .../java/org/dspace/app/rest/Application.java | 43 ++--- .../rest/BrowsesResourceControllerTest.java | 89 ++++++++++ .../rest/RootRestResourceControllerTest.java | 47 +++++ .../AbstractControllerIntegrationTest.java | 84 +++++++++ .../app/rest/test/AbstractDSpaceTest.java | 97 ++++++++++ .../test/AbstractUnitTestWithDatabase.java | 156 ++++++++++++++++ .../dspace/identifier/MockDOIConnector.java | 147 +++++++++++++++ .../src/test/resources/log4j.properties | 58 ++++++ .../src/test/resources/logback.xml | 7 + .../src/test/resources/test-config.properties | 13 ++ pom.xml | 11 +- 17 files changed, 1012 insertions(+), 35 deletions(-) create mode 100644 dspace-api/src/test/java/org/databene/contiperf/junit/ContiPerfRuleExt.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerTest.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/RootRestResourceControllerTest.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractDSpaceTest.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractUnitTestWithDatabase.java create mode 100644 dspace-spring-rest/src/test/java/org/dspace/identifier/MockDOIConnector.java create mode 100644 dspace-spring-rest/src/test/resources/log4j.properties create mode 100644 dspace-spring-rest/src/test/resources/logback.xml create mode 100644 dspace-spring-rest/src/test/resources/test-config.properties diff --git a/LICENSES_THIRD_PARTY b/LICENSES_THIRD_PARTY index 38a739ca9f..0af20d11d3 100644 --- a/LICENSES_THIRD_PARTY +++ b/LICENSES_THIRD_PARTY @@ -300,7 +300,7 @@ https://wiki.duraspace.org/display/DSPACE/Code+Contribution+Guidelines * Repackaged Cocoon Servlet Service Implementation (org.dspace.dependencies.cocoon:dspace-cocoon-servlet-service-impl:1.0.3 - http://projects.dspace.org/dspace-pom/dspace-cocoon-servlet-service-impl) * DSpace Kernel :: Additions and Local Customizations (org.dspace.modules:additions:6.0-rc4-SNAPSHOT - https://github.com/dspace/DSpace/modules/additions) * Hamcrest All (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all) - * Hamcrest Core (org.hamcrest:hamcrest-core:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-core) + * Hamcrest Core (org.hamcrest:hamcrest-all:1.3 - https://github.com/hamcrest/JavaHamcrest/hamcrest-all) * JBibTeX (org.jbibtex:jbibtex:1.0.10 - http://www.jbibtex.org) * ASM Core (org.ow2.asm:asm:4.1 - http://asm.objectweb.org/asm/) * ASM Analysis (org.ow2.asm:asm-analysis:4.1 - http://asm.objectweb.org/asm-analysis/) diff --git a/dspace-api/pom.xml b/dspace-api/pom.xml index c4ab59035d..9fcd67c1b6 100644 --- a/dspace-api/pom.xml +++ b/dspace-api/pom.xml @@ -488,7 +488,7 @@ org.hamcrest - hamcrest-core + hamcrest-all test diff --git a/dspace-api/src/test/java/org/databene/contiperf/junit/ContiPerfRuleExt.java b/dspace-api/src/test/java/org/databene/contiperf/junit/ContiPerfRuleExt.java new file mode 100644 index 0000000000..8f7ad22098 --- /dev/null +++ b/dspace-api/src/test/java/org/databene/contiperf/junit/ContiPerfRuleExt.java @@ -0,0 +1,118 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.databene.contiperf.junit; + +import java.lang.reflect.Field; + +import org.junit.internal.runners.statements.RunAfters; +import org.junit.internal.runners.statements.RunBefores; +import org.junit.runners.model.FrameworkMethod; +import org.junit.runners.model.Statement; + +/** + * That is just a workaround to support JUnit 4.12 with the ContiPerfRule. + * This performance rule extension makes sure that each statement is wrapped into + * the JUnit 4.11 format. + * + *

+ * From JUnit 4.12, the {@code fNext} field has been replaced with the + * {@code next} field on both {@code RunAfters} and {@code RunBefores} + * statements. This class is for handling both cases gracefully. + *

+ * More details about the issue can be found + * here. + * + * The lastest ContiPerf release fixes this, but is not available in the Maven repositories: + * https://github.com/lucaspouzac/contiperf/issues/8 + * + */ +public class ContiPerfRuleExt extends ContiPerfRule { + + private static final String FIELD_NAME_JUNIT_411 = "fNext"; + private static final String FIELD_NAME_JUNIT_412 = "next"; + + @Override + public Statement apply(Statement base, FrameworkMethod method, Object target) { + return super.apply(wrapStatement(base), method, target); + } + + private Statement wrapStatement(final Statement base) { + if(requiresFieldMapping(base)) { + Statement fnext = getFieldValue(FIELD_NAME_JUNIT_412, base); + if (base instanceof RunAfters) { + return new RunAfters_411((RunAfters) base, fnext); + } else if(base instanceof RunBefores) { + return new RunBefores_411((RunBefores) base, fnext); + } + return null; + } else { + return base; + } + } + + private Statement getFieldValue(final String fieldNameJunit412, final Statement base) { + try { + Field field = base.getClass().getDeclaredField(fieldNameJunit412); + return (Statement) field.get(base); + } catch (NoSuchFieldException | IllegalAccessException e) { + //ignore + return null; + } + } + + private boolean requiresFieldMapping(Statement it) { + return hasField(it, FIELD_NAME_JUNIT_412) && !hasField(it, FIELD_NAME_JUNIT_411); + } + + private boolean hasField(Statement it, String fieldName) { + try { + it.getClass().getDeclaredField(fieldName); + return true; + } catch (NoSuchFieldException e) { + //ignore + } + return false; + } + + private class RunBefores_411 extends RunBefores { + + private Statement delegate; + private Statement fNext; + + private RunBefores_411(RunBefores delegate, Statement fNext) { + // We delegate to the evaluate method anyway. + super(null, null, null); + this.delegate = delegate; + this.fNext = fNext; + } + + @Override + public void evaluate() throws Throwable { + delegate.evaluate(); + } + + } + + private class RunAfters_411 extends RunAfters { + + private Statement delegate; + private Statement fNext; + + private RunAfters_411(RunAfters delegate, Statement fNext) { + // We delegate to the evaluate method anyway. + super(null, null, null); + this.delegate = delegate; + this.fNext = fNext; + } + + @Override + public void evaluate() throws Throwable { + delegate.evaluate(); + } + } +} \ No newline at end of file diff --git a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java index fd7fb2375a..38924e9709 100644 --- a/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java +++ b/dspace-api/src/test/java/org/dspace/AbstractIntegrationTest.java @@ -8,6 +8,7 @@ package org.dspace; import org.databene.contiperf.junit.ContiPerfRule; +import org.databene.contiperf.junit.ContiPerfRuleExt; import org.junit.Ignore; import org.junit.Rule; @@ -28,5 +29,5 @@ public class AbstractIntegrationTest extends AbstractUnitTest //We only enable contiperf in the integration tests, as it doesn't //seem so useful to run them in isolated unit tests @Rule - public ContiPerfRule contiperfRules = new ContiPerfRule(); + public ContiPerfRule contiperfRules = new ContiPerfRuleExt(); } diff --git a/dspace-oai/pom.xml b/dspace-oai/pom.xml index c66b5fd84a..4cf68b88ab 100644 --- a/dspace-oai/pom.xml +++ b/dspace-oai/pom.xml @@ -225,7 +225,7 @@ org.hamcrest - hamcrest-core + hamcrest-all test diff --git a/dspace-spring-rest/pom.xml b/dspace-spring-rest/pom.xml index 10019e7943..f50c3d6262 100644 --- a/dspace-spring-rest/pom.xml +++ b/dspace-spring-rest/pom.xml @@ -32,6 +32,120 @@ + + + + test-environment + + false + + maven.test.skip + false + + + + + + + maven-dependency-plugin + 2.8 + + ${project.build.directory}/testing + + + org.dspace + dspace-parent + ${project.version} + zip + testEnvironment + + + + + + setupTestEnvironment + generate-test-resources + + unpack + + + + setupIntegrationTestEnvironment + pre-integration-test + + unpack + + + + + + + + org.codehaus.gmaven + groovy-maven-plugin + 2.0 + + + setproperty + generate-test-resources + + execute + + + + project.properties['agnostic.build.dir']=project.build.directory.replace(File.separator,'/'); + println("Initializing Maven property 'agnostic.build.dir' to: " + project.properties['agnostic.build.dir']); + + + + + + + + + maven-surefire-plugin + + + + + ${agnostic.build.dir}/testing/dspace/ + + true + + + + + + + maven-failsafe-plugin + + + + ${agnostic.build.dir}/testing/dspace/ + + true + + + + + + + + + + @@ -125,6 +239,7 @@ + org.dspace dspace-services @@ -137,7 +252,44 @@ commons-collections4 4.1 - + + + + org.jmockit + jmockit + test + + + junit + junit + test + + + org.hamcrest + hamcrest-all + test + + + com.h2database + h2 + test + + + org.databene + contiperf + test + + + org.mockito + mockito-core + test + + + com.jayway.jsonpath + json-path-assert + test + + \ No newline at end of file diff --git a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java index 4f089140ff..320b0c6de1 100644 --- a/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java +++ b/dspace-spring-rest/src/main/java/org/dspace/app/rest/Application.java @@ -19,6 +19,7 @@ import org.dspace.app.rest.filter.DSpaceRequestContextFilter; import org.dspace.app.rest.model.hateoas.DSpaceRelProvider; import org.dspace.app.rest.utils.ApplicationConfig; import org.dspace.app.util.DSpaceContextListener; +import org.dspace.kernel.DSpaceKernelManager; import org.dspace.servicemanager.DSpaceKernelImpl; import org.dspace.servicemanager.DSpaceKernelInit; import org.dspace.servicemanager.config.DSpaceConfigurationService; @@ -94,29 +95,29 @@ public class Application extends SpringBootServletInitializer { servletContext.setInitParameter("dspace.dir", configuration.getDspaceHome()); // start the kernel when the webapp starts - try { - this.kernelImpl = DSpaceKernelInit.getKernel(null); - if (!this.kernelImpl.isRunning()) { - this.kernelImpl.start(getProvidedHome(configuration.getDspaceHome())); // init the kernel - } - - //Set the DSpace Kernel Application context as a parent of the Spring Boot context so that - //we can auto-wire all DSpace Kernel services - springBootApplicationContext.setParent(kernelImpl.getServiceManager().getApplicationContext()); - - //Add a listener for Spring Boot application shutdown so that we can nicely cleanup the DSpace kernel. - springBootApplicationContext.addApplicationListener(new DSpaceKernelDestroyer(kernelImpl)); - - } catch (Exception e) { - // failed to start so destroy it and log and throw an exception + if (DSpaceKernelManager.getDefaultKernel() == null) { try { - this.kernelImpl.destroy(); - } catch (Exception e1) { - // nothing + this.kernelImpl = DSpaceKernelInit.getKernel(null); + if (!this.kernelImpl.isRunning()) { + this.kernelImpl.start(getProvidedHome(configuration.getDspaceHome())); // init the kernel + } + //Set the DSpace Kernel Application context as a parent of the Spring Boot context so that + //we can auto-wire all DSpace Kernel services + springBootApplicationContext.setParent(kernelImpl.getServiceManager().getApplicationContext()); + + //Add a listener for Spring Boot application shutdown so that we can nicely cleanup the DSpace kernel. + springBootApplicationContext.addApplicationListener(new DSpaceKernelDestroyer(kernelImpl)); + } catch (Exception e) { + // failed to start so destroy it and log and throw an exception + try { + this.kernelImpl.destroy(); + } catch (Exception e1) { + // nothing + } + String message = "Failure during ServletContext initialisation: " + e.getMessage(); + log.error(message + ":" + e.getMessage(), e); + throw new RuntimeException(message, e); } - String message = "Failure during ServletContext initialisation: " + e.getMessage(); - log.error(message + ":" + e.getMessage(), e); - throw new RuntimeException(message, e); } } diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerTest.java new file mode 100644 index 0000000000..236a7997d4 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/BrowsesResourceControllerTest.java @@ -0,0 +1,89 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static com.jayway.jsonpath.matchers.JsonPathMatchers.hasJsonPath; +import static org.hamcrest.Matchers.*; +import static org.hamcrest.text.IsEqualIgnoringCase.equalToIgnoringCase; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.hamcrest.Matcher; +import org.junit.Test; + +/** + * Integration test to test the /api/discover/browses endpoint + */ +public class BrowsesResourceControllerTest extends AbstractControllerIntegrationTest { + + @Test + public void findAll() throws Exception { + //When we call the root endpoint + mockMvc.perform(get("/api/discover/browses")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + + //Our default Discovery config has 4 browse indexes so we expect this to be reflected in the page object + .andExpect(jsonPath("$.page.size", is(20))) + .andExpect(jsonPath("$.page.totalElements", is(4))) + .andExpect(jsonPath("$.page.totalPages", is(1))) + .andExpect(jsonPath("$.page.number", is(0))) + + //The array of browse index should have a size 4 + .andExpect(jsonPath("$._embedded.browses", hasSize(4))) + + //Check that all (and only) the default browse indexes are present + .andExpect(jsonPath("$._embedded.browses", containsInAnyOrder( + dateIssuedBrowseIndex("asc"), + contributorBrowseIndex("asc"), + titleBrowseIndex("asc"), + subjectBrowseIndex("asc") + ))) + ; + } + + private Matcher subjectBrowseIndex(final String order) { + return allOf( + hasJsonPath("$.metadata", contains("dc.subject.*")), + hasJsonPath("$.metadataBrowse", is(true)), + hasJsonPath("$.order", equalToIgnoringCase(order)), + hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")) + ); + } + + private Matcher titleBrowseIndex(final String order) { + return allOf( + hasJsonPath("$.metadata", contains("dc.title")), + hasJsonPath("$.metadataBrowse", is(false)), + hasJsonPath("$.order", equalToIgnoringCase(order)), + hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")) + ); + } + + private Matcher contributorBrowseIndex(final String order) { + return allOf( + hasJsonPath("$.metadata", contains("dc.contributor.*", "dc.creator")), + hasJsonPath("$.metadataBrowse", is(true)), + hasJsonPath("$.order", equalToIgnoringCase(order)), + hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")) + ); + } + + private Matcher dateIssuedBrowseIndex(final String order) { + return allOf( + hasJsonPath("$.metadata", contains("dc.date.issued")), + hasJsonPath("$.metadataBrowse", is(false)), + hasJsonPath("$.order", equalToIgnoringCase(order)), + hasJsonPath("$.sortOptions[*].name", containsInAnyOrder("title", "dateissued", "dateaccessioned")) + ); + } + +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/RootRestResourceControllerTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RootRestResourceControllerTest.java new file mode 100644 index 0000000000..9385285c08 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/RootRestResourceControllerTest.java @@ -0,0 +1,47 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest; + +import static org.hamcrest.Matchers.startsWith; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import org.dspace.app.rest.test.AbstractControllerIntegrationTest; +import org.junit.Test; + + +/** + * Integration test for the {@link RootRestResourceController} + */ +public class RootRestResourceControllerTest extends AbstractControllerIntegrationTest { + + @Test + public void listDefinedEndpoint() throws Exception { + + //When we call the root endpoint + mockMvc.perform(get("/api")) + //The status has to be 200 OK + .andExpect(status().isOk()) + //We expect the content type to be "application/hal+json;charset=UTF-8" + .andExpect(content().contentType(contentType)) + //Check that all required root links are present and that they are absolute + .andExpect(jsonPath("$._links.bitstreamformats.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.bitstreams.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.browses.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.collections.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.communities.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.epersons.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.groups.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.items.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.metadatafields.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.metadataschemas.href", startsWith("http://localhost/api"))) + .andExpect(jsonPath("$._links.sites.href", startsWith("http://localhost/api"))) + ; + } + +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java new file mode 100644 index 0000000000..4ffbc0eab2 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractControllerIntegrationTest.java @@ -0,0 +1,84 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.test; + +import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppContextSetup; + +import java.util.Arrays; +import java.util.List; + +import javax.servlet.Filter; + +import org.apache.commons.io.Charsets; +import org.dspace.app.rest.Application; +import org.dspace.app.rest.utils.ApplicationConfig; +import org.junit.Assert; +import org.junit.Before; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.hateoas.MediaTypes; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.TestExecutionListeners; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.test.context.support.DependencyInjectionTestExecutionListener; +import org.springframework.test.context.support.DirtiesContextTestExecutionListener; +import org.springframework.test.context.transaction.TransactionalTestExecutionListener; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.context.WebApplicationContext; + +/** + * Abstract controller integration test class that will take care of setting up the + * environment to run the integration test + */ +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = {Application.class, ApplicationConfig.class}) +@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, + TransactionalTestExecutionListener.class}) +@DirtiesContext +@WebAppConfiguration +public class AbstractControllerIntegrationTest extends AbstractUnitTestWithDatabase { + + protected MediaType contentType = new MediaType(MediaTypes.HAL_JSON.getType(), + MediaTypes.HAL_JSON.getSubtype(), Charsets.UTF_8); + + protected MockMvc mockMvc; + + protected HttpMessageConverter mappingJackson2HttpMessageConverter; + + @Autowired + private WebApplicationContext webApplicationContext; + + @Autowired + private List requestFilters; + + @Autowired + void setConverters(HttpMessageConverter[] converters) { + + this.mappingJackson2HttpMessageConverter = Arrays.asList(converters).stream().filter( + hmc -> hmc instanceof MappingJackson2HttpMessageConverter).findAny().get(); + + Assert.assertNotNull("the JSON message converter must not be null", + this.mappingJackson2HttpMessageConverter); + } + + @Before + public void setUp() throws Exception { + super.setUp(); + this.mockMvc = webAppContextSetup(webApplicationContext) + //Add all filter implementations + .addFilters(requestFilters.toArray(new Filter[requestFilters.size()])) + .build(); + } + +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractDSpaceTest.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractDSpaceTest.java new file mode 100644 index 0000000000..1f9ef403fa --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractDSpaceTest.java @@ -0,0 +1,97 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.test; + +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.net.URL; +import java.sql.SQLException; +import java.util.Properties; +import java.util.TimeZone; + +import org.apache.log4j.Logger; +import org.dspace.servicemanager.DSpaceKernelImpl; +import org.dspace.servicemanager.DSpaceKernelInit; +import org.junit.AfterClass; +import org.junit.BeforeClass; + +/** + * Abstract Test class copied from DSpace API + */ +public class AbstractDSpaceTest +{ + /** log4j category */ + private static final Logger log = Logger.getLogger(AbstractDSpaceTest.class); + + /** + * Test properties. These configure our general test environment + */ + protected static Properties testProps; + + /** + * DSpace Kernel. Must be started to initialize ConfigurationService and + * any other services. + */ + protected static DSpaceKernelImpl kernelImpl; + + /** + * This method will be run before the first test as per @BeforeClass. It will + * initialize shared resources required for all tests of this class. + * + * This method loads our test properties to initialize our test environment, + * and then starts the DSpace Kernel (which allows access to services). + */ + @BeforeClass + public static void initKernel() + { + try + { + //set a standard time zone for the tests + TimeZone.setDefault(TimeZone.getTimeZone("Europe/Dublin")); + + //load the properties of the tests + testProps = new Properties(); + URL properties = AbstractDSpaceTest.class.getClassLoader() + .getResource("test-config.properties"); + testProps.load(properties.openStream()); + + // Initialise the service manager kernel + kernelImpl = DSpaceKernelInit.getKernel(null); + if (!kernelImpl.isRunning()) + { + // NOTE: the "dspace.dir" system property MUST be specified via Maven + kernelImpl.start(System.getProperty("dspace.dir")); // init the kernel + } + } + catch (IOException ex) + { + log.error("Error initializing tests", ex); + fail("Error initializing tests: " + ex.getMessage()); + } + } + + + /** + * This method will be run after all tests finish as per @AfterClass. It + * will clean resources initialized by the @BeforeClass methods. + */ + @AfterClass + public static void destroyKernel() throws SQLException { + //we clear the properties + testProps.clear(); + testProps = null; + + //Also clear out the kernel & nullify (so JUnit will clean it up) + if (kernelImpl != null) { + kernelImpl.destroy(); + } + kernelImpl = null; + } +} + diff --git a/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractUnitTestWithDatabase.java b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractUnitTestWithDatabase.java new file mode 100644 index 0000000000..a249faad5f --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/app/rest/test/AbstractUnitTestWithDatabase.java @@ -0,0 +1,156 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.app.rest.test; + +import static org.junit.Assert.fail; + +import java.sql.SQLException; + +import org.apache.log4j.Logger; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.Context; +import org.dspace.core.I18nUtil; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.factory.EPersonServiceFactory; +import org.dspace.eperson.service.EPersonService; +import org.dspace.storage.rdbms.DatabaseUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; + +/** + * Abstract Test class that will initialize the in-memory database + */ +public class AbstractUnitTestWithDatabase extends AbstractDSpaceTest { + /** log4j category */ + private static final Logger log = Logger.getLogger(AbstractUnitTestWithDatabase.class); + + /** + * Context mock object to use in the tests. + */ + protected Context context; + + /** + * EPerson mock object to use in the tests. + */ + protected EPerson eperson; + + /** + * This method will be run before the first test as per @BeforeClass. It will + * initialize shared resources required for all tests of this class. + *

+ * NOTE: Per JUnit, "The @BeforeClass methods of superclasses will be run before those the current class." + * http://junit.org/apidocs/org/junit/BeforeClass.html + *

+ * This method builds on the initialization in AbstractDSpaceTest, and + * initializes the in-memory database for tests that need it. + */ + @BeforeClass + public static void initDatabase() + { + // Clear our old flyway object. Because this DB is in-memory, its + // data is lost when the last connection is closed. So, we need + // to (re)start Flyway from scratch for each Unit Test class. + DatabaseUtils.clearFlywayDBCache(); + + try + { + // Update/Initialize the database to latest version (via Flyway) + DatabaseUtils.updateDatabase(); + } + catch(SQLException se) + { + log.error("Error initializing database", se); + fail("Error initializing database: " + se.getMessage() + + (se.getCause() == null ? "" : ": " + se.getCause().getMessage())); + } + } + + /** + * This method will be run before every test as per @Before. It will + * initialize resources required for each individual unit test. + * + * Other methods can be annotated with @Before here or in subclasses + * but no execution order is guaranteed + */ + @Before + public void setUp() throws Exception { + try + { + //Start a new context + context = new Context(Context.Mode.BATCH_EDIT); + context.turnOffAuthorisationSystem(); + + //Find our global test EPerson account. If it doesn't exist, create it. + EPersonService ePersonService = EPersonServiceFactory.getInstance().getEPersonService(); + eperson = ePersonService.findByEmail(context, "test@email.com"); + if(eperson == null) + { + // This EPerson creation should only happen once (i.e. for first test run) + log.info("Creating initial EPerson (email=test@email.com) for Unit Tests"); + eperson = ePersonService.create(context); + eperson.setFirstName(context, "first"); + eperson.setLastName(context, "last"); + eperson.setEmail("test@email.com"); + eperson.setCanLogIn(true); + eperson.setLanguage(context, I18nUtil.getDefaultLocale().getLanguage()); + // actually save the eperson to unit testing DB + ePersonService.update(context, eperson); + } + // Set our global test EPerson as the current user in DSpace + context.setCurrentUser(eperson); + + // If our Anonymous/Administrator groups aren't initialized, initialize them as well + EPersonServiceFactory.getInstance().getGroupService().initDefaultGroupNames(context); + + context.restoreAuthSystemState(); + } + catch (AuthorizeException ex) + { + log.error("Error creating initial eperson or default groups", ex); + fail("Error creating initial eperson or default groups in AbstractUnitTest init()"); + } + catch (SQLException ex) + { + log.error(ex.getMessage(),ex); + fail("SQL Error on AbstractUnitTest init()"); + } + } + + /** + * This method will be run after every test as per @After. It will + * clean resources initialized by the @Before methods. + * + * Other methods can be annotated with @After here or in subclasses + * but no execution order is guaranteed + */ + @After + public void destroy() + { + // Cleanup our global context object + try { + cleanupContext(context); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + /** + * Utility method to cleanup a created Context object (to save memory). + * This can also be used by individual tests to cleanup context objects they create. + */ + protected void cleanupContext(Context c) throws SQLException { + // If context still valid, abort it + if(c!=null && c.isValid()) + c.complete(); + + // Cleanup Context object by setting it to null + if(c!=null) + c = null; + } +} diff --git a/dspace-spring-rest/src/test/java/org/dspace/identifier/MockDOIConnector.java b/dspace-spring-rest/src/test/java/org/dspace/identifier/MockDOIConnector.java new file mode 100644 index 0000000000..c88d9929a4 --- /dev/null +++ b/dspace-spring-rest/src/test/java/org/dspace/identifier/MockDOIConnector.java @@ -0,0 +1,147 @@ +/** + * The contents of this file are subject to the license and copyright + * detailed in the LICENSE and NOTICE files at the root of the source + * tree and available online at + * + * http://www.dspace.org/license/ + */ +package org.dspace.identifier; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import mockit.Mock; +import mockit.MockUp; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.identifier.doi.DOIConnector; +import org.dspace.identifier.doi.DOIIdentifierException; + +/** + * + * @author Pascal-Nicolas Becker (p dot becker at tu hyphen berlin dot de) + */ +public class MockDOIConnector +extends MockUp +implements DOIConnector +{ + + public Map reserved; + public Map registered; + + public MockDOIConnector() + { + reserved = new HashMap(); + registered = new HashMap(); + } + + public void reset() + { + reserved.clear(); + registered.clear(); + } + + @Override + @Mock + public boolean isDOIReserved(Context context, String doi) + throws DOIIdentifierException + { + return reserved.containsKey(doi); + } + + @Override + @Mock + public boolean isDOIRegistered(Context context, String doi) + throws DOIIdentifierException + { + return registered.containsKey(doi); + } + + @Override + @Mock + public void deleteDOI(Context context, String doi) + throws DOIIdentifierException + { + if (reserved.remove(doi) == null) + { + throw new DOIIdentifierException("Trying to delete a DOI that was " + + "never reserved!", DOIIdentifierException.DOI_DOES_NOT_EXIST); + } + registered.remove(doi); + } + + @Override + @Mock + public void reserveDOI(Context context, DSpaceObject dso, String doi) + throws DOIIdentifierException + { + UUID itemId = reserved.get(doi); + if (null != itemId) + { + if (dso.getID().equals(itemId)) + { + return; + } + else + { + throw new DOIIdentifierException("Trying to reserve a DOI that " + + "is reserved for another object.", + DOIIdentifierException.MISMATCH); + } + } + reserved.put(doi, dso.getID()); + } + + @Override + @Mock + public void registerDOI(Context context, DSpaceObject dso, String doi) + throws DOIIdentifierException + { + if (!reserved.containsKey(doi)) + { + throw new DOIIdentifierException("Trying to register an unreserverd " + + "DOI.", DOIIdentifierException.RESERVE_FIRST); + } + + if (!reserved.get(doi).equals(dso.getID())) + { + throw new DOIIdentifierException("Trying to register a DOI that is" + + " reserved for another item.", DOIIdentifierException.MISMATCH); + } + + if (registered.containsKey(doi)) + { + if (registered.get(doi).equals(dso.getID())) + { + return; + } + else + { + throw new DOIIdentifierException("Trying to register a DOI that " + + "is registered for another item.", + DOIIdentifierException.MISMATCH); + } + } + + registered.put(doi, dso.getID()); + } + + @Override + @Mock + public void updateMetadata(Context context, DSpaceObject dso, String doi) + throws DOIIdentifierException + { + if (!reserved.containsKey(doi)) + { + throw new DOIIdentifierException("Trying to update a DOI that is not " + + "registered!", DOIIdentifierException.DOI_DOES_NOT_EXIST); + } + if (!reserved.get(doi).equals(dso.getID())) + { + throw new DOIIdentifierException("Trying to update metadata of an " + + "unreserved DOI.", DOIIdentifierException.DOI_DOES_NOT_EXIST); + } + } + +} \ No newline at end of file diff --git a/dspace-spring-rest/src/test/resources/log4j.properties b/dspace-spring-rest/src/test/resources/log4j.properties new file mode 100644 index 0000000000..c611e482fd --- /dev/null +++ b/dspace-spring-rest/src/test/resources/log4j.properties @@ -0,0 +1,58 @@ +# +# The contents of this file are subject to the license and copyright +# detailed in the LICENSE and NOTICE files at the root of the source +# tree and available online at +# +# http://www.dspace.org/license/ +# +########################################################################### +# +# log4j.properties +# +# +########################################################################### + +# This is a copy of the log4j configuration file for DSpace, to avoid +# getting errors when running tests. + +# Set root category priority to INFO and its only appender to A1. +log4j.rootCategory=INFO, A1 + +# A1 is set to be a ConsoleAppender. +log4j.appender.A1=org.apache.log4j.ConsoleAppender + +# A1 uses PatternLayout. +log4j.appender.A1.layout=org.apache.log4j.PatternLayout +log4j.appender.A1.layout.ConversionPattern=%d %-5p %c @ %m%n + +########################################################################### +# Other settings +########################################################################### + +# Block passwords from being exposed in Axis logs. +# (DEBUG exposes passwords in Basic Auth) +log4j.logger.org.apache.axis.handlers.http.HTTPAuthHandler=INFO + +# Block services logging except on exceptions +log4j.logger.org.dspace.kernel=ERROR +log4j.logger.org.dspace.services=ERROR +log4j.logger.org.dspace.servicemanager=ERROR +log4j.logger.org.dspace.providers=ERROR +log4j.logger.org.dspace.utils=ERROR + +log4j.appender.stdout=org.apache.log4j.ConsoleAppender +log4j.appender.stdout.Target=System.out +log4j.appender.stdout.layout=org.apache.log4j.PatternLayout +log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n +# +# Root logger option +log4j.rootLogger=INFO, stdout + +# Hibernate logging options (INFO only shows startup messages) +log4j.logger.org.hibernate=INFO + +# For detailed Hibernate logging in Unit Tests, you can enable the following +# setting which logs all JDBC bind parameter runtime arguments. +# This will drastically increase the size of Unit Test logs though. +#log4j.logger.org.hibernate.SQL=DEBUG, A1 +#log4j.logger.org.hibernate.type=TRACE, A1 diff --git a/dspace-spring-rest/src/test/resources/logback.xml b/dspace-spring-rest/src/test/resources/logback.xml new file mode 100644 index 0000000000..97e893b607 --- /dev/null +++ b/dspace-spring-rest/src/test/resources/logback.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/dspace-spring-rest/src/test/resources/test-config.properties b/dspace-spring-rest/src/test/resources/test-config.properties new file mode 100644 index 0000000000..273d93c968 --- /dev/null +++ b/dspace-spring-rest/src/test/resources/test-config.properties @@ -0,0 +1,13 @@ +# +# The contents of this file are subject to the license and copyright +# detailed in the LICENSE and NOTICE files at the root of the source +# tree and available online at +# +# http://www.dspace.org/license/ +# +# Defines the test folder where the unit tests will be run +test.folder = ./target/testing/ +test.folder.assetstore = ./target/testing/dspace/assetstore + +#Path for a test file to create bitstreams +test.bitstream = ./target/testing/dspace/assetstore/ConstitutionofIreland.pdf diff --git a/pom.xml b/pom.xml index 383029b4b1..b1352dcffc 100644 --- a/pom.xml +++ b/pom.xml @@ -1342,15 +1342,22 @@ junit junit - 4.11 + 4.12 test org.hamcrest - hamcrest-core + hamcrest-all 1.3 test + + + com.jayway.jsonpath + json-path-assert + 2.2.0 + test + com.h2database