package edu.kit.aifb.cumulus.webapp;
import static edu.kit.aifb.cumulus.WebTestUtils.EMPTY_STRINGS;
import static edu.kit.aifb.cumulus.WebTestUtils.buildLiteral;
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.junit.Assert.fail;
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.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.junit.Test;
import org.openrdf.model.Literal;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.rio.ntriples.NTriplesUtil;
import edu.kit.aifb.cumulus.framework.Environment;
import edu.kit.aifb.cumulus.framework.Environment.ConfigParams;
import edu.kit.aifb.cumulus.webapp.HttpProtocol.Headers;
import edu.kit.aifb.cumulus.webapp.HttpProtocol.Methods;
import edu.kit.aifb.cumulus.webapp.HttpProtocol.MimeTypes;
import edu.kit.aifb.cumulus.webapp.HttpProtocol.Parameters;
/**
* Test case for Put REST service ({@link CRUDServlet}) on a triple store.
*
* @author Andreas Wagner
* @author Andrea Gazzarini
* @since 1.0
*/
public class CRUDServletPutTest extends AbstractCRUDServletTest {
/**
* Creates a mock {@link HttpServletRequest} with given params.
*
* @param s the subject.
* @param p the predicate.
* @param o the object.
* @param c the context.
* @param s2 the new subject.
* @param p2 the new predicate.
* @param o2 the new object.
* @param c2 the new context.
* @return a mock {@link HttpServletRequest}.
*/
protected HttpServletRequest createMockHttpRequest(
final Value s, final Value p, final Value o, final Value c,
final Value s2, final Value p2, final Value o2, final Value c2) {
final HttpServletRequest request = super.createMockHttpRequest(null, s, p, o, c);
when(request.getParameter(Parameters.S2)).thenReturn(s2 == null ? null : NTriplesUtil.toNTriplesString(s2));
when(request.getParameter(Parameters.P2)).thenReturn(p2 == null ? null : NTriplesUtil.toNTriplesString(p2));
when(request.getParameter(Parameters.O2)).thenReturn(o2 == null ? null : NTriplesUtil.toNTriplesString(o2));
when(request.getParameter(Parameters.C2)).thenReturn(c2 == null ? null : NTriplesUtil.toNTriplesString(c2));
when(request.getRequestDispatcher(anyString())).thenReturn(mock(RequestDispatcher.class));
return request;
}
/**
* If CumulusStoreException 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 putWithCumulusInternalServerFailure() throws Exception {
when(_context.getAttribute(ConfigParams.STORE)).thenReturn(newTripleStore());
final URI uri = buildResource(randomString());
final HttpServletRequest request = createMockHttpRequest(uri, null, null, null, uri, null, null, null);
when(request.getHeader(Headers.CONTENT_TYPE)).thenReturn(MimeTypes.TEXT_PLAIN);
final HttpServletResponse response = mock(HttpServletResponse.class);
_classUnderTest.doPut(request, response);
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
/**
* The old triple must contain at least one constant.
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithInvalidInput() throws Exception {
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(null, null, null, null, null, null, null, null);
final ServletInputStream stream = new ServletInputStream() {
final InputStream _inputStream = new ByteArrayInputStream(EMPTY_STRINGS[0].getBytes(Environment.CHARSET_UTF8));
@Override
public int read() throws IOException {
return _inputStream.read();
}
};
when(request.getInputStream()).thenReturn(stream);
_classUnderTest.doPut(request, response);
verify(response).setStatus(HttpServletResponse.SC_BAD_REQUEST);
}
/**
* 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 putWithInvalidUri() throws Exception {
final String invalidUri = randomString();
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getParameter(Parameters.S)).thenReturn(invalidUri);
when(request.getHeader(Headers.CONTENT_TYPE)).thenReturn(MimeTypes.TEXT_PLAIN);
when(request.getRequestDispatcher(anyString())).thenReturn(mock(RequestDispatcher.class));
when(request.getMethod()).thenReturn(Methods.POST);
final HttpServletResponse response = mock(HttpServletResponse.class);
_classUnderTest.doPut(request, response);
verify(response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
/**
* If the old triple has only constants the update will affect only that triple.
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithOneMatchingTriple() throws Exception {
final URI subject = buildResource("http://gridpedia.org/id/Actor");
final URI predicate = buildResource("http://www.w3.org/2000/01/rdf-schema#label");
final Literal object = buildLiteral("Actor");
final Literal newObject = buildLiteral("Attore");
final Value[] pattern = new Value[]{subject, predicate, object};
final Value[] expectedTriple = new Value[]{subject, predicate, newObject};
assertEquals(1, numOfRes(TRIPLE_STORE.query(pattern)));
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(subject, predicate, object, null, null, null, newObject, null);
_classUnderTest.doPut(request, response);
assertEquals(1, numOfRes(TRIPLE_STORE.query(expectedTriple)));
assertEquals(0, numOfRes(TRIPLE_STORE.query(pattern)));
}
/**
* If the old triple is a constant and it doesn't match anything then the new triple will be applied.
*
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithOneUnmatchingTriple() throws Exception {
final URI unexistentSubject = buildResource(randomString());
final URI unexistentPredicate = buildResource(randomString());
final Literal unexistentObject = buildLiteral(randomString());
final Literal unexistentNewObject = buildLiteral(randomString());
final Value[] pattern = new Value[]{unexistentSubject, unexistentPredicate, unexistentObject};
assertEquals(0, numOfRes(TRIPLE_STORE.query(pattern)));
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(
unexistentSubject, unexistentPredicate, unexistentObject,
null, null, null, unexistentNewObject, null);
_classUnderTest.doPut(request, response);
assertEquals(0, numOfRes(TRIPLE_STORE.query(pattern)));
assertEquals(1, numOfRes(TRIPLE_STORE.query(new Value[]{unexistentSubject, unexistentPredicate, unexistentNewObject})));
}
/**
* If the old triple is a constant and it doesn't match anything then a new triple will be created.
* In this first scenario the new triple pattern is not valorized...so that means the old (unexistent triple)
* will be created.
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithOneUnmatchingTripleAndNoNewTriple() throws Exception {
final URI unexistentSubject = buildResource(randomString());
final URI unexistentPredicate = buildResource(randomString());
final Literal unexistentObject = buildLiteral(randomString());
final Value[] pattern = new Value[]{unexistentSubject, unexistentPredicate, unexistentObject};
assertEquals(0, numOfRes(TRIPLE_STORE.query(pattern)));
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(
unexistentSubject, unexistentPredicate, unexistentObject,
null, null, null, null, null);
_classUnderTest.doPut(request, response);
assertEquals(1, numOfRes(TRIPLE_STORE.query(pattern)));
}
/**
* If the old triple is a pattern and it doesn't match anything then a 404 (NOT_FOUND) code must be returned.
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithPatternAndNoMatchingData() throws Exception {
final URI unexistentUri = buildResource(randomString());
assertEquals(0, numOfRes(TRIPLE_STORE.describe(unexistentUri, false)));
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(unexistentUri, null, null, null, null, null, null, null);
_classUnderTest.doPut(request, response);
verify(response).setStatus(HttpServletResponse.SC_NOT_FOUND);
}
/**
* If old triple is a pattern then the first matching triple will be updated.
*
* @throws Exception hopefully never, otherwise the test fails.
*/
@Test
public void putWithSeveralMatchingTriples() throws Exception {
final URI subject = buildResource("http://gridpedia.org/id/Actor");
final Literal newObject = buildLiteral("This is the value that will be set for the first matching Actor triple.");
final Value[] pattern = new Value[]{subject, null, null};
int howManyTriplesForActorAsSubject = numOfRes(TRIPLE_STORE.query(pattern));
assertTrue(howManyTriplesForActorAsSubject > 0);
final HttpServletResponse response = mock(HttpServletResponse.class);
final HttpServletRequest request = createMockHttpRequest(subject, null, null, null, null, null, newObject, null);
_classUnderTest.doPut(request, response);
int howManyTriplesFoundAfterUpdating = 0;
boolean oneTripleHasBeenUpdated = false;
for (final Iterator<Statement> iterator = TRIPLE_STORE.query(pattern); iterator.hasNext();) {
final Statement triple = iterator.next();
if (newObject.equals(triple.getObject()))
{
if (oneTripleHasBeenUpdated)
{
fail("Another triple has already been updated.");
} else
{
oneTripleHasBeenUpdated = true;
}
}
howManyTriplesFoundAfterUpdating++;
}
assertTrue(oneTripleHasBeenUpdated);
assertEquals(howManyTriplesFoundAfterUpdating, howManyTriplesForActorAsSubject);
}
}