Writing unit test for file uploader in Junit/Mockito - java

I am trying to write a unit test for following component to upload file:
#Component("uploader")
public class FileUploader {
public List<FileItem> processFileUploadRequest(HttpServletRequest request) throws FileUploadException {
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletContext servletContext = request.getServletContext();
File repository = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
factory.setRepository(repository);
ServletFileUpload upload = new ServletFileUpload(factory);
return upload.parseRequest(request);
}
}
I have written unit test using junit/mockito like following:
#Test
public void testProcessFileUploadRequestSuccess() throws FileUploadException {
HttpServletRequest request = Mockito.mock(HttpServletRequest.class);
ServletContext servletContext = Mockito.mock(ServletContext.class);
Mockito.when(request.getServletContext()).thenReturn(servletContext);
Mockito.when(servletContext.getAttribute("javax.servlet.context.tempdir")).thenReturn(this.servletTmpDir);
Assert.assertNotNull(fileUploader.processFileUploadRequest(request));
}
I am getting the following error:
org.apache.commons.fileupload.FileUploadBase$InvalidContentTypeException: the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is null
at org.apache.commons.fileupload.FileUploadBase$FileItemIteratorImpl.<init>(FileUploadBase.java:947)
...
Can anyone please give any clue regarding this? Thank you.

This error ...
the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is null
... is a result of your HttpServletRequest not having a multipart content type.
You can fix this by adding the following line to your test case:
Mockito.when(request.getContentType()).thenReturn("multipart/form-data; boundary=someBoundary");
Ona side note: your question (specifically, this part: #Component("uploader")) suggests that you are using Spring. If so, then perhaps your file upload code could be more easily tested using Spring's MockMvcRequestBuilders#fileUpload(String, Object...) to return a MockMultipartHttpServletRequestBuilder. Something like this:
mockMvc.perform(MockMvcRequestBuilders.fileUpload("/upload")
.file(aFile)
.andExpect(status().is(200))
.andExpect(content().string("..."));

Related

Upload a file to a rest server using apache camel

I need to sync a file from a folder to a rest endpoint. So if a file is placed in a specific folder I need to send that file to a REST endpoint accepting multipart files. I am using apache camel to achieve this.
The REST endpoint is written in Spring and is as below:
#PostMapping("/")
public String handleFileUpload(#RequestParam("file") MultipartFile file,
RedirectAttributes redirectAttributes) {
storageService.store(file);
redirectAttributes.addFlashAttribute("message",
"You successfully uploaded " + file.getOriginalFilename() + "!");
return "redirect:/";
}
I am new to Camel and have figured out how to poll the directory by building a route and fetch the file, but I am unable to figure out how this route has to be used to put this file to the rest endpoint. This is what I have tried:
#Component
public class SampleCamelRouter extends RouteBuilder {
#Override
public void configure() throws Exception {
from("file://target/input?delay=4000")
.process(new Processor() {
public void process(Exchange exchange) throws Exception {
File filetoUpload = exchange.getIn().getBody(File.class);
String fileName = exchange.getIn().getHeader(Exchange.FILE_NAME, String.class);
MultipartEntityBuilder entity = MultipartEntityBuilder.create();
entity.addTextBody("fileName", fileName);
entity.addBinaryBody("file", filetoUpload);
ByteArrayOutputStream out = new ByteArrayOutputStream();
entity.build().writeTo(out);
InputStream inputStream = new ByteArrayInputStream(out.toByteArray());
exchange.getIn().setBody(inputStream);
}
})
.setHeader(Exchange.CONTENT_TYPE, constant("multipart/form-data"))
.setHeader(Exchange.HTTP_METHOD, constant("POST"))
.to("http://localhost:9090/");
}
}
But when I run this, I get the below exception in the rest server:
org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
And on the camel side the request fails with a 500 error.
Any leads are appreciated.
use headers = "Content-Type= multipart/form-data", into controller.
The problem isn't in your code - it's in your request. You're missing boundary in your multipart request. As it said in specification here:
The Content-Type field for multipart entities requires one parameter, "boundary", which is used to specify the encapsulation boundary. The encapsulation boundary is defined as a line consisting entirely of two hyphen characters ("-", decimal code 45) followed by the boundary parameter value from the Content-Type header field.
This and this posts should also be helpful.

Uploading a file via Spring

I have a dev server which revolves angular 2 at localhost: 4200, and tomcat with Spring on localhost: 8080.
I try to upload a file to the server in the following manner:
angular code:
uploadAvatar(file:File){
let xhr = new XMLHttpRequest()
xhr.open("POST",`http://localhost:8080/api/upload/avatar`)
xhr.setRequestHeader("Content-Type","multipart/form-data")
xhr.send(file)
}
Controller code Spring:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST)
public String uploadFile(MultipartFile file){
log.info(file);
return file.getName();
}
But after trying to download a file error appears in the java-console:
org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request;
nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
How do I fix this error?
Thank you.
UPDATE 1
The "duplicate" is used with Spring MVC + JSP, I'm trying to download a file via Ajax. And the version of the decision does not help me given there.
UPDATE 2
Spring Boot(v1.4.3.RELEASE)
I use the java configuration, if you want I will give an example of a full configuration.
I found the solution to my problem, I postorabs below describe in detail what I did for this.
As the presentation I use Angular 2, sending the file takes place in the following manner.
uploadFile(file:File){
let form = new FormData();
form.append("file",file)
let xhr = new XMLHttpRequest()
xhr.open("POST",`${URL}/api/upload/avatar`)
xhr.send(form)
}
Content-Type and the boundary in this case, are automatically linked.
The following manipulations need to be done on the server Stronie:
Add two bean:
#Bean(name = "commonsMultipartResolver")
public MultipartResolver multipartResolver() {
return new StandardServletMultipartResolver();
}
#Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize("10MB");
factory.setMaxRequestSize("10MB");
return factory.createMultipartConfig();
}
The controller looks like this:
#RequestMapping(value = "/api/upload/avatar", method = RequestMethod.POST,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public String uploadFile(#RequestPart MultipartFile file){
log.info(file);
return file.getOriginalFilename();
}

Spring MVC : Return CSS

My goal is to merge/minify all css files and return the result as String.
Here's my Spring test method :
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET, produces = "text/css")
#ResponseBody
public void css(HttpServletResponse response) {
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
response.flushBuffer();
is.close();
}
}
This is working with Chrome, Firefox and Safari but not with IE and Opera.
After some checks in the inspectors, the URL https://host/project/stylesheet.css is loading in each browsers. I can see the content but it does not seem to be recognized as text/css.
Also, even with produces = "text/css", I can not see the content-type http header in all browsers.
Error log in IE :
CSS ignored because of mime type incompatibility
Does anyone know how to correctly do this?
Working code :
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET)
public ResponseEntity<Void> css(HttpServletResponse response) {
response.setContentType("text/css");
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
IOUtils.closeQuietly(is);
}
response.flushBuffer();
return new ResponseEntity<Void>(HttpStatus.OK);
}
I suspect the problem is due to your usage of HttpServletResponse.flushBuffer().
As the API of HttpServletRequest states:
Forces any content in the buffer to be written to the client. A call
to this method automatically commits the response, meaning the status
code and headers will be written.
My assumption would be that Spring attempts to set the Content-Type header on the HttpServletResponse after the method on your controller has returned. However, because you have committed the response with your call to HttpServletResponse.flushBuffer(), it cannot do this.
I would try either:
Injecting the HttpServletResponse into your controller and setting the header yourself in code before calling HttpServletResponse.flushBuffer()
Removing your usage of HttpServletRequest.flushBuffer()
Since you're writing the content directly to the output stream, you don't need to use #ResponseBody. You just need to ensure that you set the Content-Type response header. Also, it'd be better to return a ResponseEntity (rather than void) to indicate to Spring that you're handling the response yourself.
#RequestMapping(value = "/stylesheet.css", method = RequestMethod.GET)
public ResponseEntity css(HttpServletResponse response) {
// Set the content-type
response.setHeader("Content-Type", "text/css");
File path = new File(servletContext.getRealPath("/WEB-INF/includes/css/"));
File[] files = path.listFiles(...);
for (File file : files) {
InputStream is = new FileInputStream(file);
IOUtils.copy(is, response.getOutputStream());
IOUtils.closeQuietly(is);
}
response.flushBuffer();
return new ResponseEntity(HttpStatus.OK)
}

How to Unit Test handling of incoming Jersey MultiPart requests

We have a REST service that accepts MultiPart POST requests containing BodyParts that hold InputStreams. Inside the REST service a file might be created based on the provided data.
Task
We want to unit test the class that does the file operations based on its MultiPart input. Note: Wo do NOT want to use Jersey-Test! Grizzly does not load our spring application context which we need to inject DAO and fileHandler services into our REST service class. We explicitly want to test how our fileHandler service processes multiPart data.
The problem however is that the MultiPart that is sent out from the REST Client is not the same as the one received by the REST Server as jersey probably does something with the data to stream it or whatever. Trying to test (see below) the following setup will result in an
IllegalArgumentException [B cannot be cast to com.sun.jersey.multipart.BodyPartEntity
REST Client - sending a MultiPart
(just snippets, I omitted the obvious stuff):
byte[] bytes = FileManager.readImageFileToArray(completePath, fileType);
MultiPart multiPart = new MultiPart().
bodyPart(new BodyPart(bytes, MediaType.APPLICATION_OCTET_STREAM_TYPE)).
bodyPart(new BodyPart(fileName, MediaType.APPLICATION_XML_TYPE)).
bodyPart(new BodyPart(senderId, MediaType.APPLICATION_XML_TYPE));
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(MultiPartWriter.class);
Client client = Client.create(cc);
WebResource webResource = client.resource(requestUrl);
Builder builder = webResource.type(MediaType.MULTIPART_FORM_DATA_TYPE);
builder = addHeaderParams(builder, headerParams);
ClientResponse response = builder.post(ClientResponse.class, multiPart);
Server Side - receiving a MultiPart
REST:
#POST
#Consumes(MediaType.MULTIPART_FORM_DATA)
#Produces(MediaType.APPLICATION_JSON)
#Transactional
public Response create(MultiPart multiPart) {
try {
multiPartReader.saveFile(multiPart);
Server Side MultiPartReader to save file from multipart
public class MultiPartReader {
public void saveFile(MultiPart multiPart) throws IOException {
BodyPartEntity bpe = (BodyPartEntity) multiPart.getBodyParts().get(0).getEntity();
InputStream inputStream = bpe.getInputStream();
// ...
BufferedImage bi = ImageIO.read(inputStream);
String fileName = getFileNameFromMultiPart(multiPart);
File file = new File(filename);
if (file.isDirectory()) {
ImageIO.write(bi, formatName, file);
} else {
file.mkdirs();
ImageIO.write(bi, formatName, file);
}
bpe.close();
}
Test - handling an incoming MultiPart in isolation
Now I want to test the MultiPartReader:
#Test
public void saveFile_should_Create_file() throws IOException {
byte[] bytes = IOUtils.toByteArray(this.getClass().getResourceAsStream(fileResource));
MultiPart multiPart = new MultiPart().
bodyPart(new BodyPart(bytes, MediaType.APPLICATION_OCTET_STREAM_TYPE)).
bodyPart(new BodyPart(fileName, MediaType.APPLICATION_XML_TYPE)).
bodyPart(new BodyPart(senderId, MediaType.APPLICATION_XML_TYPE));
multiPartReader.saveFile(multiPart);
file = new File(fileName);
Assert.assertNotNull(file);
Assert.assertTrue(file.getTotalSpace() > 0);
file.delete();
}
But, like I said I get a
IllegalArgumentException [B cannot be cast to com.sun.jersey.multipart.BodyPartEntity
at
BodyPartEntity bpe = (BodyPartEntity) multiPart.getBodyParts().get(0).getEntity();
So what can I do to emulate the send/receive handled by jersey so that my test will get the same data as my REST service does deployed on a server and requested by a REST client?
EDIT
Using
BodyPartEntity bpe = multiPart.getBodyParts().get(0).getEntityAs(BodyPartEntity.class);
will throw a
IllegalStateException: Entity instance does not contain the unconverted content
Further pointer, I think, towards having to convert the test-generated MultiPart in some way, before calling my MultiPartReader..
There has to be some method in jersey, I can call that will do this converting just the way it does, when it sends out a MultiPart request on a deployed system or maybe it is the receiving end that does some parsing when receiving the HTTP request..?
Looking at the jersey-multipart docs I see:
"It is not currently possible to know ahead of time what Java class the application would prefer to use for each individual body part, so an appropriate Provider cannot be selected. Currently, the unparsed content of each body part is returned (as a byte array) in the entity property of the returned BodyPart} instance, and the application can decide what further steps are needed based on the headers included in that body part. The simplest technique is to examine the received BodyPart, and then call the getEntityAs() method once you know which implementation class you would prefer."
It looks like you need to follow that suggestion. Examine the byte array returned in your Server Side MultiPartReader code:
multiPart.getBodyParts().get(0).getEntity();
...and call getEntityAs() on the BodyPart.

YUI Uploader with Java back-end

I am trying to use the (flash based) YUI Uploader with a Java (Spring-based) back-end.
The typical way of uploading files in the Java Servlet world is to set the ENCTYPE='multipart/form-data' on the HTML form requesting the file from the user. With the right server side APIs (i.e. Commons FileUpload), it is possible to get the file on the server.
But I am stymied by how to achieve this with the YUI Uploader. I am able to reach the Java controller, and I am even able to extract the custom post values. But I have no idea how to extract the binary file data out of the request.
Has anyone out had any luck with a YUI uploader with a Java back-end?
To answer my own question, and to make a long story short, this snippet of code did the trick:
#Controller
#RequestMapping("/FileUploadController")
public class FileUploadController {
#RequestMapping(method = RequestMethod.POST)
protected ModelAndView onSubmit(HttpServletRequest request) throws Exception{
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> /* FileItem */ items = upload.parseRequest(request);
for (FileItem fileItem : items) {
if (fileItem.isFormField()) {
// processFormField(fileItem);
} else {
File uploadedFile = new File("/tmp/junk/" + fileItem.getName());
fileItem.write(uploadedFile);
}
}
return new ModelAndView("index");
}
}
This example uses Spring, but you should be able to do exactly the same as long as you have HttpServletRequest object.

Categories