DS-3484: Initial setup of Spring MVC test + RootRestResourceControllerTest

This commit is contained in:
Tom Desair
2017-10-06 16:45:10 +02:00
parent 260e4178df
commit 876d0e9a82
17 changed files with 1012 additions and 35 deletions

View File

@@ -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/)

View File

@@ -488,7 +488,7 @@
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -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.
*
* <p>
* 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.
* <p>
* More details about the issue can be found
* <a href="https://github.com/lucaspouzac/contiperf/issues/9">here</a>.
*
* The lastest ContiPerf release fixes this, but is not available in the Maven repositories:
* <a href="https://github.com/lucaspouzac/contiperf/issues/8">https://github.com/lucaspouzac/contiperf/issues/8</a>
*
*/
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();
}
}
}

View File

@@ -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();
}

View File

@@ -225,7 +225,7 @@
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>

View File

@@ -32,6 +32,120 @@
<!-- <springdata.commons>1.13.0.RELEASE</springdata.commons> -->
</properties>
<profiles>
<!-- If Unit Testing is enabled, then setup the Unit Test Environment.
See also the 'skiptests' profile in Parent POM. -->
<profile>
<id>test-environment</id>
<activation>
<activeByDefault>false</activeByDefault>
<property>
<name>maven.test.skip</name>
<value>false</value>
</property>
</activation>
<build>
<plugins>
<!-- Unit/Integration Testing setup: This plugin unzips the
'testEnvironment.zip' file (created by dspace-parent POM), into
the 'target/testing/' folder, to essentially create a test
install of DSpace, against which Tests can be run. -->
<plugin>
<artifactId>maven-dependency-plugin</artifactId>
<version>2.8</version>
<configuration>
<outputDirectory>${project.build.directory}/testing</outputDirectory>
<artifactItems>
<artifactItem>
<groupId>org.dspace</groupId>
<artifactId>dspace-parent</artifactId>
<version>${project.version}</version>
<type>zip</type>
<classifier>testEnvironment</classifier>
</artifactItem>
</artifactItems>
</configuration>
<executions>
<execution>
<id>setupTestEnvironment</id>
<phase>generate-test-resources</phase>
<goals>
<goal>unpack</goal>
</goals>
</execution>
<execution>
<id>setupIntegrationTestEnvironment</id>
<phase>pre-integration-test</phase>
<goals>
<goal>unpack</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- This plugin allows us to run a Groovy script in our Maven POM
(see: http://gmaven.codehaus.org/Executing+Groovy+Code )
We are generating a OS-agnostic version (agnostic.build.dir) of
the ${project.build.directory} property (full path of target dir).
This is needed by the FileWeaver & Surefire plugins (see below)
to initialize the Unit Test environment's dspace.cfg file.
Otherwise, the Unit Test Framework will not work on Windows OS.
This Groovy code was mostly borrowed from:
http://stackoverflow.com/questions/3872355/how-to-convert-file-separator-in-maven
-->
<plugin>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>groovy-maven-plugin</artifactId>
<version>2.0</version>
<executions>
<execution>
<id>setproperty</id>
<phase>generate-test-resources</phase> <!-- XXX I think this should be 'initialize' - MHW -->
<goals>
<goal>execute</goal>
</goals>
<configuration>
<source>
project.properties['agnostic.build.dir']=project.build.directory.replace(File.separator,'/');
println("Initializing Maven property 'agnostic.build.dir' to: " + project.properties['agnostic.build.dir']);
</source>
</configuration>
</execution>
</executions>
</plugin>
<!-- Run Unit Testing! This plugin just kicks off the tests (when enabled). -->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- Specify the dspace.dir to use for test environment -->
<!-- This system property is loaded by AbstractDSpaceTest to initialize the test environment -->
<dspace.dir>${agnostic.build.dir}/testing/dspace/</dspace.dir>
<!-- Turn off any DSpace logging -->
<dspace.log.init.disable>true</dspace.log.init.disable>
</systemPropertyVariables>
</configuration>
</plugin>
<!-- Run Integration Testing! This plugin just kicks off the tests (when enabled). -->
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<systemPropertyVariables>
<!-- Specify the dspace.dir to use for test environment -->
<dspace.dir>${agnostic.build.dir}/testing/dspace/</dspace.dir>
<!-- Turn off any DSpace logging -->
<dspace.log.init.disable>true</dspace.log.init.disable>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<dependencies>
<!-- These next two dependencies build a WAR that is BOTH executable
AND deployable into an external container (Tomcat).
@@ -67,18 +181,18 @@
<artifactId>spring-boot-starter-data-rest</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- <dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>${spring-boot.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
<version>${json-path.version}</version>
</dependency> -->
<!-- <dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<scope>test</scope>
<version>${json-path.version}</version>
</dependency> -->
<!-- The HAL Browser -->
<dependency>
@@ -125,6 +239,7 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.dspace</groupId>
<artifactId>dspace-services</artifactId>
@@ -137,7 +252,44 @@
<artifactId>commons-collections4</artifactId>
<version>4.1</version>
</dependency>
<!-- TEST DEPENDENCIES -->
<dependency> <!-- Keep jmockit before junit -->
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.databene</groupId>
<artifactId>contiperf</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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);
}
}

View File

@@ -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<? super Object> 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<? super Object> 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<? super Object> 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<? super Object> 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"))
);
}
}

View File

@@ -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")))
;
}
}

View File

@@ -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<Filter> 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();
}
}

View File

@@ -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;
}
}

View File

@@ -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.
* <p>
* NOTE: Per JUnit, "The @BeforeClass methods of superclasses will be run before those the current class."
* http://junit.org/apidocs/org/junit/BeforeClass.html
* <p>
* 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;
}
}

View File

@@ -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<DOIConnector>
implements DOIConnector
{
public Map<String, UUID> reserved;
public Map<String, UUID> registered;
public MockDOIConnector()
{
reserved = new HashMap<String, UUID>();
registered = new HashMap<String, UUID>();
}
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);
}
}
}

View File

@@ -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

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<logger name="org.springframework.web" level="INFO"/>
<logger name="org.hibernate" level="INFO"/>
<logger name="net.sf.ehcache" level="INFO"/>
</configuration>

View File

@@ -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

11
pom.xml
View File

@@ -1342,15 +1342,22 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-core</artifactId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<!-- https://github.com/json-path/JsonPath/tree/master/json-path-assert -->
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path-assert</artifactId>
<version>2.2.0</version>
<scope>test</scope>
</dependency>
<!-- H2 is an in-memory database used for Unit/Integration tests -->
<dependency>
<groupId>com.h2database</groupId>