package edu.kit.aifb.cumulus.webapp; import static edu.kit.aifb.cumulus.WebTestUtils.EMPTY_STRINGS; import static edu.kit.aifb.cumulus.WebTestUtils.SELECT_ALL_TRIPLES_PATTERN; import static edu.kit.aifb.cumulus.WebTestUtils.buildResource; import static edu.kit.aifb.cumulus.WebTestUtils.newTripleStore; import static edu.kit.aifb.cumulus.WebTestUtils.numOfRes; import static edu.kit.aifb.cumulus.WebTestUtils.randomString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.Arrays; import javax.servlet.RequestDispatcher; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.junit.Test; import org.openrdf.model.URI; import org.openrdf.model.Value; import org.openrdf.model.vocabulary.RDF; import org.openrdf.model.vocabulary.RDFS; import edu.kit.aifb.cumulus.framework.Environment.ConfigParams; import edu.kit.aifb.cumulus.webapp.HttpProtocol.Methods; import edu.kit.aifb.cumulus.webapp.HttpProtocol.Parameters; /** * Test case for Delete REST service ({@link CRUDServlet}) on a triple store. * * @author Andreas Wagner * @author Andrea Gazzarini * @since 1.0 */ public class CRUDServletDeleteTest extends AbstractCRUDServletTest { private String _aThing = "http://gridpedia.org/id/Thing"; private String[] _unmatchingUri = { "http://gridpedia.org/id/Thing2", "http://gridpedia.org/id/Actor2", "http://www.w3.org/2000/01/rdf-schema#Class2"}; private String[] _deleteUri = { _aThing, "http://www.w3.org/2000/01/rdf-schema#Class", "http://gridpedia.org/id/Actor"}; private Value[][] _deletePatterns = { new Value[] {null, null, buildResource(_aThing) }, new Value[] {null, RDF.TYPE, null }, new Value[] {buildResource("http://gridpedia.org/id/Actor"), RDFS.SEEALSO, buildResource("http://dbpedia.org/resource/Actant") } }; private Value[][] _unmatchingPatterns = { new Value[] {null, null, buildResource(randomString()) }, new Value[] {null, buildResource(randomString()), buildResource(randomString())}, new Value[] {null, buildResource(randomString()), null }, new Value[] {buildResource(randomString()), null, null}}; /** * If a "uri" parameter is specified {@link CRUDServlet} will delete the * corresponding entity. On top of receiving a valid URI parameter it will * use a DESCRIBE to delete the whole entity description (i.e. triples where * that URI is subject or object). * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteEntity() throws Exception { for (final String uriAsString : _deleteUri) { final URI uri = buildResource(uriAsString); final int howManyTriplesBeforeDeleting = numOfRes(TRIPLE_STORE.describe(uri, false)); assertTrue("There must be at least 1 triple describing entity " + uri, howManyTriplesBeforeDeleting > 0); _classUnderTest.doDelete(createMockHttpRequest(uri, null, null, null, null), mock(HttpServletResponse.class)); final int howManyTriplesAfterDeleting = numOfRes(TRIPLE_STORE.describe(uri, false)); assertEquals("Entity has not (completely) deleted.", 0, howManyTriplesAfterDeleting); } } /** * If a "uri" parameter is specified {@link CRUDServlet} will delete the * corresponding entity. On top of receiving a valid URI parameter it will * use a DESCRIBE to delete the whole entity description (i.e. triples where * that URI is subject or object). * * If there are no matching triples that describe the given entity then * delete won't have any effect on the store. * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteEntityWithNoMatchingData() throws Exception { for (final String uriAsString : _unmatchingUri) { final URI uri = buildResource(uriAsString); final int howManyTriplesForThatEntityBeforeDeleting = numOfRes(TRIPLE_STORE.describe(uri, false)); final int howManyTriplesOnStoreBeforeDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertEquals("There must be no triple matching for entity " + uriAsString, 0, howManyTriplesForThatEntityBeforeDeleting); assertTrue("At least one triple must exist and it mustn't match DESCRIBE " + uriAsString, howManyTriplesOnStoreBeforeDeleting > 0); final HttpServletResponse response = mock(HttpServletResponse.class); final HttpServletRequest request = createMockHttpRequest(uri, null, null, null, null); _classUnderTest.doDelete(request, response); verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND); final int howManyTriplesOnStoreAfterDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertEquals(howManyTriplesOnStoreBeforeDeleting, howManyTriplesOnStoreAfterDeleting); } } /** * A delete command is not considered valid if all input parameters are * null. The service will return BAD_REQUEST http status code and the store * won't have any changes. * * @throws Exception hopefully never, otherwise the test fails. */ @Test public void deleteWithInvalidInput() throws Exception { final int howManyTriplesOnStoreBeforeDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertTrue("At least one triple must exists on the store.", howManyTriplesOnStoreBeforeDeleting > 0); internalDeleteWithInvalidInput(null, null, null, null, null); for (final String invalidParameter : EMPTY_STRINGS) { internalDeleteWithInvalidInput(invalidParameter, null, null, null, null); internalDeleteWithInvalidInput(null, invalidParameter, null, null, null); internalDeleteWithInvalidInput(null, null, invalidParameter, null, null); internalDeleteWithInvalidInput(null, null, null, invalidParameter, null); internalDeleteWithInvalidInput(null, null, null, null, invalidParameter); } final int howManyTriplesOnStoreAfterDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertEquals(howManyTriplesOnStoreBeforeDeleting, howManyTriplesOnStoreAfterDeleting); } /** * {@link CRUDServlet} must be able to delete by pattern. In this case the * uri parameter must be null (otherwise a deletionByEntity will be * executed) and system will compose a deletion pattern by using s,p,o,c * parameters. * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteByPattern() throws Exception { for (final Value[] pattern : _deletePatterns) { final int howManyTriplesBeforeDeleting = numOfRes(TRIPLE_STORE.query(pattern)); assertTrue("A minimum of 1 matching triple is needed for pattern " + Arrays.toString(pattern), howManyTriplesBeforeDeleting > 0); _classUnderTest.doDelete(createMockHttpRequest(null, pattern[0], pattern[1], pattern[2], null), mock(HttpServletResponse.class)); final int howManyTriplesAfterDeleting = numOfRes(TRIPLE_STORE.query(pattern)); assertEquals("Delete not executed or executed partially.", 0, howManyTriplesAfterDeleting); } } /** * If an internal server error occurs during deletion the service must * answer with 500 HTTP status code. Note that this doesn't cover any * possible scenarios...just emulating an uncaught exception in order to see * if a correct response code is returned. * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteWithUnknownInternalServerFailure() throws Exception { when(_context.getAttribute(ConfigParams.STORE)).thenReturn(null); final HttpServletRequest request = createMockHttpRequest(null, buildResource(randomString()), null, null, null); final HttpServletResponse response = mock(HttpServletResponse.class); _classUnderTest.doDelete(request, response); verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } /** * If CumulusStoreExceptionm occurs during deletion the service must * answer with 500 HTTP status code. Note that this doesn't cover any * possible scenarios...it just uses an uninitialized store in order to see * if a correct response code is returned. * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteWithCumulusInternalServerFailure() throws Exception { when(_context.getAttribute(ConfigParams.STORE)).thenReturn(newTripleStore()); final HttpServletRequest request = createMockHttpRequest(null, buildResource(randomString()), null, null, null); final HttpServletResponse response = mock(HttpServletResponse.class); _classUnderTest.doDelete(request, response); verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } /** * If given pattern doesn't match any triple on the store the delete command * won't have any effect. * * @throws Exception * hopefully never, otherwise the test fails. */ @Test public void deleteByPatternWithNoMatchingData() throws Exception { for (final Value[] pattern : _unmatchingPatterns) { final int howManyTriplesForThatPatternBeforeDeleting = numOfRes(TRIPLE_STORE.query(pattern)); final int howManyTriplesOnStoreBeforeDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertEquals("There must be no triple matching pattern " + Arrays.toString(pattern), 0, howManyTriplesForThatPatternBeforeDeleting); assertTrue("At least one triple must exist and it mustn't match pattern " + Arrays.toString(pattern), howManyTriplesOnStoreBeforeDeleting > 0); final HttpServletResponse response = mock(HttpServletResponse.class); final HttpServletRequest request = createMockHttpRequest(null, pattern[0], pattern[1], pattern[2], null); _classUnderTest.doDelete(request, response); verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND); final int howManyTriplesOnStoreAfterDeleting = numOfRes(TRIPLE_STORE.query(SELECT_ALL_TRIPLES_PATTERN)); assertEquals(howManyTriplesOnStoreBeforeDeleting, howManyTriplesOnStoreAfterDeleting); } } /** * Internal method used for testing several invalid input scenarios. * * @param uri the URI (delete by entity). * @param s the subject. * @param p the predicate. * @param o the object. * @param c the context. * * @throws Exception hopefully never, otherwise the test fails. */ private void internalDeleteWithInvalidInput( final String uri, final String s, final String p, final String o, final String c) throws Exception { final HttpServletResponse response = mock(HttpServletResponse.class); final HttpServletRequest request = mock(HttpServletRequest.class); when(request.getParameter(Parameters.URI)).thenReturn(uri != null ? uri : null); when(request.getParameter(Parameters.S)).thenReturn(s == null ? null : String.valueOf(s)); when(request.getParameter(Parameters.P)).thenReturn(p == null ? null : String.valueOf(p)); when(request.getParameter(Parameters.O)).thenReturn(o == null ? null : String.valueOf(o)); when(request.getParameter(Parameters.C)).thenReturn(c == null ? null : String.valueOf(c)); when(request.getRequestDispatcher(anyString())).thenReturn(mock(RequestDispatcher.class)); when(request.getMethod()).thenReturn(Methods.POST); _classUnderTest.doDelete(request, response); verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST); } }