package eu.europeana.cloud.service.mcs.rest;
import com.google.common.collect.ImmutableMap;
import com.google.common.hash.Hashing;
import com.google.common.io.ByteStreams;
import eu.europeana.cloud.common.model.DataProvider;
import eu.europeana.cloud.common.model.File;
import eu.europeana.cloud.common.model.Representation;
import eu.europeana.cloud.common.response.ErrorInfo;
import eu.europeana.cloud.common.web.ParamConstants;
import eu.europeana.cloud.service.mcs.ApplicationContextUtils;
import eu.europeana.cloud.service.mcs.RecordService;
import eu.europeana.cloud.service.mcs.UISClientHandler;
import eu.europeana.cloud.service.mcs.exception.CannotModifyPersistentRepresentationException;
import eu.europeana.cloud.service.mcs.exception.RepresentationNotExistsException;
import eu.europeana.cloud.service.mcs.status.McsErrorCode;
import eu.europeana.cloud.test.CassandraTestRunner;
import org.glassfish.jersey.client.ClientConfig;
import org.glassfish.jersey.media.multipart.FormDataMultiPart;
import org.glassfish.jersey.media.multipart.MultiPartFeature;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.context.ApplicationContext;
import javax.ws.rs.Path;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import static javax.ws.rs.core.MediaType.APPLICATION_OCTET_STREAM_TYPE;
import static org.junit.Assert.*;
/**
* FileResourceTest
*/
@RunWith(CassandraTestRunner.class)
public class FileResourceTest extends JerseyTest {
private RecordService recordService;
private Representation rep;
private File file;
private WebTarget fileWebTarget;
private UISClientHandler uisHandler;
@Before
public void mockUp() throws Exception {
ApplicationContext applicationContext = ApplicationContextUtils
.getApplicationContext();
recordService = applicationContext.getBean(RecordService.class);
uisHandler = applicationContext.getBean(UISClientHandler.class);
DataProvider dataProvider = new DataProvider();
dataProvider.setId("1");
Mockito.doReturn(new DataProvider()).when(uisHandler)
.getProvider("1");
Mockito.doReturn(true).when(uisHandler)
.existsCloudId(Mockito.anyString());
rep = recordService.createRepresentation("1", "1", "1");
file = new File();
file.setFileName("fileName");
file.setMimeType(APPLICATION_OCTET_STREAM_TYPE.toString());
Map<String, Object> allPathParams = ImmutableMap
.<String, Object> of(ParamConstants.P_CLOUDID,
rep.getCloudId(), ParamConstants.P_REPRESENTATIONNAME,
rep.getRepresentationName(), ParamConstants.P_VER,
rep.getVersion(), ParamConstants.P_FILENAME,
file.getFileName());
fileWebTarget = target(
FileResource.class.getAnnotation(Path.class).value())
.resolveTemplates(allPathParams);
}
@After
public void cleanUp() throws Exception {
recordService.deleteRepresentation(rep.getCloudId(),
rep.getRepresentationName());
}
@Override
public Application configure() {
return new JerseyConfig().property("contextConfigLocation",
"classpath:spiedPersistentServicesTestContext.xml");
}
@Override
protected void configureClient(ClientConfig config) {
config.register(MultiPartFeature.class);
}
@Test
public void shouldReturnContentWithinRangeOffset() throws Exception {
// given particular content in service
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when part of file is requested (skip first byte)
Response getFileResponse = fileWebTarget.request()
.header("Range", "bytes=1-").get();
assertEquals(Response.Status.PARTIAL_CONTENT.getStatusCode(),
getFileResponse.getStatus());
// then retrieved content should consist of second and third byte of
// inserted byte array
InputStream responseStream = getFileResponse
.readEntity(InputStream.class);
byte[] responseContent = ByteStreams.toByteArray(responseStream);
byte[] expectedResponseContent = copyOfRange(content, 1,
content.length - 1);
assertArrayEquals("Read data is different from requested range",
expectedResponseContent, responseContent);
}
int[][] parameters = new int[][] { { 1, 2 }, { 0, 0 }, { 0, 1 }, { 3, 3 },
{ 0, 3 }, { 3, 4 } };
/**
* Yeah, this test really sucks. It should use Parameterized test (as it was
* implemented earlier - see the anotaions down below commented out). But it
* is impossible to use 2 runners in one test.
*
* @throws Exception
*/
@Test
public void shouldReturnContentWithinRangeForParameters() throws Exception {
for (int[] elem : parameters) {
shouldReturnContentWithinRange(elem[0], elem[1]);
}
}
// @Test
// @Parameters({ "1,2", "0,0", "0,1", "3,3", "0,3", "3,4" })
public void shouldReturnContentWithinRange(Integer rangeStart,
Integer rangeEnd) throws Exception {
// given particular content in service
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when part of file is requested (2 bytes with 1 byte offset)
Response getFileResponse = fileWebTarget
.request()
.header("Range",
String.format("bytes=%d-%d", rangeStart, rangeEnd))
.get();
assertEquals(Response.Status.PARTIAL_CONTENT.getStatusCode(),
getFileResponse.getStatus());
// then retrieved content should consist of second and third byte of
// inserted byte array
InputStream responseStream = getFileResponse
.readEntity(InputStream.class);
byte[] responseContent = ByteStreams.toByteArray(responseStream);
byte[] expectedResponseContent = copyOfRange(content, rangeStart,
rangeEnd);
assertArrayEquals("Read data is different from requested range",
expectedResponseContent, responseContent);
}
/**
* Copy the specified range of array to a new array. This method works
* similar to {@link Arrays#copyOfRange(byte[], int, int)}, but final index
* is inclusive.
*
* @see Arrays#copyOfRange(boolean[], int, int)
*/
private byte[] copyOfRange(byte[] originalArray, int start, int end) {
if (end > originalArray.length - 1) {
end = originalArray.length - 1;
}
return Arrays.copyOfRange(originalArray, start, end + 1);
}
@Test
public void shouldReturnErrorWhenRequestedRangeNotSatisfiable()
throws Exception {
// given particular content in service
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when unsatisfiable content range is requested
Response getFileResponse = fileWebTarget.request()
.header("Range", "bytes=4-5").get();
// then should response that requested range is not satisfiable
assertEquals(
Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE.getStatusCode(),
getFileResponse.getStatus());
}
@Test
public void shouldReturnErrorWhenRequestedRangeNotValid() throws Exception {
// given particular content in service
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when part of file is requested (2 bytes with 1 byte offset)
Response getFileResponse = fileWebTarget.request()
.header("Range", "bytes=-2").get();
// then should response that request is wrongly formatted
assertEquals(
Response.Status.REQUESTED_RANGE_NOT_SATISFIABLE.getStatusCode(),
getFileResponse.getStatus());
}
@Test
@Ignore(value = "TODO: implement")
public void shouldReturnErrorOnHashMismatch() {
}
@Test
public void shouldOverrideFileOnRepeatedPut() throws Exception {
// given particular content in service
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when you override it with another content
byte[] contentModified = { 5, 6, 7 };
String contentModifiedMd5 = Hashing.md5().hashBytes(contentModified)
.toString();
FormDataMultiPart multipart = new FormDataMultiPart().field(
ParamConstants.F_FILE_MIME, file.getMimeType()).field(
ParamConstants.F_FILE_DATA,
new ByteArrayInputStream(contentModified),
MediaType.APPLICATION_OCTET_STREAM_TYPE);
Response putFileResponse = fileWebTarget.request().put(
Entity.entity(multipart, multipart.getMediaType()));
assertEquals("Unexpected status code",
Response.Status.NO_CONTENT.getStatusCode(),
putFileResponse.getStatus());
// then the content in service should be also modivied
ByteArrayOutputStream baos = new ByteArrayOutputStream();
String retrievedFileMd5 = recordService.getContent(rep.getCloudId(),
rep.getRepresentationName(), rep.getVersion(),
file.getFileName(), baos);
assertArrayEquals("Read data is different from written",
contentModified, baos.toByteArray());
assertEquals("MD5 checksum is different than written",
contentModifiedMd5, retrievedFileMd5);
}
@Test
public void shouldDeleteFile() throws Exception {
// given particular (random in this case) content in service
byte[] content = new byte[1000];
ThreadLocalRandom.current().nextBytes(content);
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
Response deleteFileResponse = fileWebTarget.request().delete();
assertEquals("Unexpected status code",
Response.Status.NO_CONTENT.getStatusCode(),
deleteFileResponse.getStatus());
Representation representation = recordService
.getRepresentation(rep.getCloudId(),
rep.getRepresentationName(), rep.getVersion());
assertTrue(representation.getFiles().isEmpty());
}
@Test
public void shouldReturn404WhenDeletingNonExistingFile() {
Response deleteFileResponse = fileWebTarget.request().delete();
assertEquals("Unexpected status code",
Response.Status.NOT_FOUND.getStatusCode(),
deleteFileResponse.getStatus());
ErrorInfo deleteErrorInfo = deleteFileResponse
.readEntity(ErrorInfo.class);
assertEquals(McsErrorCode.FILE_NOT_EXISTS.toString(),
deleteErrorInfo.getErrorCode());
}
@Test
public void shouldReturn404WhenUpdatingNotExistingFile() throws Exception {
// given particular (random in this case) content
byte[] content = new byte[1000];
ThreadLocalRandom.current().nextBytes(content);
// when content is added to record representation
FormDataMultiPart multipart = new FormDataMultiPart().field(
ParamConstants.F_FILE_MIME, file.getMimeType()).field(
ParamConstants.F_FILE_DATA, new ByteArrayInputStream(content),
MediaType.APPLICATION_OCTET_STREAM_TYPE);
Response putFileResponse = fileWebTarget.request().put(
Entity.entity(multipart, multipart.getMediaType()));
assertEquals("Unexpected status code",
Response.Status.NOT_FOUND.getStatusCode(),
putFileResponse.getStatus());
}
@Test
public void shouldRetrieveContent() throws Exception {
// given particular (random in this case) content in service
byte[] content = new byte[1000];
ThreadLocalRandom.current().nextBytes(content);
String contentMd5 = Hashing.md5().hashBytes(content).toString();
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when this file is requested
Response getFileResponse = fileWebTarget.request().get();
assertEquals(Response.Status.OK.getStatusCode(),
getFileResponse.getStatus());
// then concent should be equal to the previously put
InputStream responseStream = getFileResponse
.readEntity(InputStream.class);
byte[] responseContent = ByteStreams.toByteArray(responseStream);
assertArrayEquals("Read data is different from written", content,
responseContent);
assertEquals("File content tag mismatch", contentMd5, getFileResponse
.getEntityTag().getValue());
}
@Test
public void shouldReturnCorrectHeaderForHeadRequest() throws CannotModifyPersistentRepresentationException, RepresentationNotExistsException {
byte[] content = { 1, 2, 3, 4 };
recordService.putContent(rep.getCloudId(), rep.getRepresentationName(),
rep.getVersion(), file, new ByteArrayInputStream(content));
// when part of file is requested (skip first byte)
Response getFileResponse = fileWebTarget.request().head();
assertEquals(getFileResponse.getStatus(), 200);
String locationHeader = getFileResponse.getHeaderString("Location");
String fileUri = fileWebTarget.getUri().toString();
assertEquals(locationHeader, fileUri);
}
}