MockMvc losing file during multipart call - mockmvc

I'm trying to test a file upload using MockMvc, here is the code snippet:
final Map<String, String> boundary = new HashMap<>() {{ put("boundary", "1000000"); }};
final MockMultipartFile file = new MockMultipartFile("name", "name.ext", "multipart/form-data", "some data".getBytes());
mockMvc.perform(
multipart("/path/to/request")
.file(file)
.contentType(new MediaType("multipart", "form-data", boundary))
).andExpect(status().isOk())
The call goes through. The method processing the request has only one parameter - a MultipartFile. During a real execution, the method receives the file, no problem. However, when running the mock code above, the method always receives a null - the mock file is getting lost somewhere between the perform(...) and the actual method call.
Any help is appreciated.

The issue was with the file name. It shouldn't be "name", it should have been "file", since this is what the caller was expecting.

Related

Multipart Request in Flutter with MultipartFile.fromBytes

I am trying to send a file to an API with Flutter and the http library.
From a file selector, I get the bytes of the file (a pdf) and create a MultipartRequest as follows:
var request = http.MultipartRequest('PUT', Uri.parse('https://xxx/files'))
..fields['grade'] = 'xxx'
..fields['candidate'] = 'xxx'
..files.add(http.MultipartFile.fromBytes(
'document', file.bytes!, contentType: MediaType('application', 'pdf')));
var response = await request.send();
The request failed, it seems that no binary content is added to this request.
In debug mode I can see that the _stream attribute of the file is null (I am sure that file.bytes contains the file) :
content of the debugging
What did I miss?
I found the problem. Without the optional FileName parameter of the fromBytes method the request is not correctly created. You just need to specify a file name for it to work.

Data return from axios cant be written to variable

I want to get an data attribute from my axios post and write it to an local variable to reuse it.
If i console.log it inside the axios .then, tha data is set, if i write it to my variable and want to use it after, it is empty.
export default {
data(){
return {
post:{},
projectId: '',
existingProjects: []
}
},
methods: {
addPost(){
//check if project exists else create
let uriProj = 'http://localhost:4000/projects/add';
this.axios.post(uriProj, {
projectName: this.post.project,
}).then(response => this.projectId = response.data.data);
console.log("project_id: "+this.projectId)
}
}
What am i doing wrong?
Another Question:
Is this the right way if i want to reuse the id in another method?
My Goal is to first create a project if it is not already in my db, then i want to reuse the id of the created or returned project model to create a new customer in my db, if the customer already has the project with the id of this project, it shouldnt be added, if it is a new one it should be added.
Has this to be done in multiple requests or is there a simple method for doing this?
I believe the issue you are seeing has to do with the asynchronous nature of network calls. When axios submits the post request it returns a Promise then the addPost function continues executing. So the projectId gets logged after it the initial value gets set, but before the network request completes. Everything inside the then() function executes once the network request has been completed so you can test by moving the console.log to be executed once the request is done. You could also output the value in the template so you can see it update {{ projectId }}
this.axios.post(uriProj, {
projectName: this.post.project,
}).then(response => {
this.projectId = response.data.data
console.log("project_id: "+this.projectId)
});
I would ideally recommend using the VueJS dev tools browser extension because it allows you to inspect the state of your Vue components without having to use console.log or add random echos to your template markup.
#jfadich is correct. I recommend using async/await instead of then, it's more intuitive to read.
async addPost(){
//check if project exists else create
let uriProj = 'http://localhost:4000/projects/add';
let resp = await this.axios.post(uriProj, {
projectName: this.post.project,
})
this.projectId = resp.data.data
console.log("project_id: "+this.projectId)
}

PlayFramework Testing: Uploading File in Fake Request Errors

I want to test my method, which requires uploading a file. It is initialized like this:
val tempFile = TemporaryFile(new java.io.File("/home/ophelia/Desktop/students"))
val part = FilePart[TemporaryFile](
key = "students",
filename = "students",
contentType = Some("text/plain"),
ref = tempFile)
val files = Seq[FilePart[TemporaryFile]](part)
val formData = MultipartFormData(
dataParts = Map(),
files = Seq(part),
badParts = Seq(),
missingFileParts = Seq())
I pass it into the FakeRequest:
val result = route(
FakeRequest(POST, "/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session " + testSessionA.id.string))
.withMultipartFormDataBody(formData)
)
But when I run the test I get the following error:
Cannot write an instance of play.api.mvc.AnyContentAsMultipartFormData to HTTP response. Try to define a Writeable[play.api.mvc.AnyContentAsMultipartFormData]
What am I doing wrong and how to fix it? I looked on the internet, I didnt find any useful way to understand and resolve this problem.
It's important to remember that http requests are entirely text. route() takes an implicit Writeable to convert the body type of the provided request into text. Without the right Writeable, there is no way to know how to turn MultipartFormData into a request body.
There doesn't seem to be a Writeable for MultipartFormData, but you can provide your own. jroper has a great Writeable you could use for reference. (EDIT: That code is buggy, here's a working Writeable for AnyContentAsMultipartFormData)
Once you have your Writeable, you will need to make it accessible to your call to route(). Bear in mind, you currently have a FakeRequest[AnyContentAsMultipartFormData], not a FakeRequest[MultipartFormData]. You can either convert your request first:
val request = FakeRequest(POST,
"/api/courses/"+"4f3c4ec9-46bf-4a05-a0b2-886c2040f2f6"+"/import" )
.withHeaders("Authorization" -> ("Session "))
.withMultipartFormDataBody(formData)
route(request.map(_.mdf).asInstanceOf[FakeRequest[MultipartFormData[TemporaryFile]]])
or make your Writeable a Writeable[AnyContentAsMultipartFormData].
route for a given Request[T] requires an implicit parameter of type Writeable[T] that knows how to serialize the request body, because it will actually call the controller action just like an actual web request would, by pushing bytes onto it.
The problem is that there is no Writeable[MultipartFormData] predefined (you can see which are in play.api.test.Writeables).
This means you basically have two options:
write your own Writeable that serializes a MultipartFormData into bytes
Skip the routing part and call the action directly instead, like in the accepted answer in Play Framework Testing using MultipartFormData in a FakeRequest. This way of testing actions takes a shortcut and does not actually serialize and deserialize the request.
IMHO the first option is way too much pain for the gain, but if you go down that road, maybe contribute it to play when you succeed.
One of the possible solutions is to use wsUrl. For example
"File uploading action" should {
"upload sent file and result in ID" in {
val file = Paths.get(getClass.getResource("/1.txt").toURI)
val action = wsUrl("/upload").post(Source.single(FilePart("file", "hello.txt", Option("text/plain"), FileIO.fromPath(file))))
val res = Await.result(action, timeout)
res.status mustBe OK
res.body contains "123"
}
}

UTF-8 encoded characters from REST-query not rendered properly

I'm consuming an external REST service that provides all content as UTF-8 encoded.
For some reason my application cannot properly handle the response. If I dump the response I will se things like LuleÃ¥ (should be Luleå).
EDIT:
The same behavior happens if i forward (without altering) the string to the UI, ex.:
flash.message = "Test" + integrationService.testEncoding()
What I did was to create a _Events.groovy file in the /script folder and specifying there that
eventConfigureTomcat = { tomcat ->
tomcat.connector.URIEncoding = "UTF-8"
tomcat.connector.useBodyEncodingForURI = true
}
I also have the following in my Config.groovy:
grails.views.gsp.encoding = "UTF-8"
grails.converters.encoding = "UTF-8"
But that changed nothing. The response is still wrongly shown. I'm not sure if this is a configuration issue with Grails, with the embedded tomcat or with something else. I'm currently running my test setup on windows 7, but the same issue happens on my server running on Centos. Please advice.
EDIT2:
If i consume the REST service using curl, everything is rendered correctly in the output.
EDIT3:
I'm using org.springframework.web.client.RestTemplate and HttpComponents to consume the service:
private static final HttpHeaders requestHeaders
static{
requestHeaders = new HttpHeaders()
requestHeaders.set(HttpHeaders.CONTENT_TYPE, "application/json")
requestHeaders.set(HttpHeaders.ACCEPT, "application/json")
requestHeaders.set("Accept-Encoding", "gzip")
}
private final static RestTemplate restTemplate = new RestTemplate(new HttpComponentsClientHttpRequestFactory(
HttpClientBuilder.create().build()))
...
...
public def testEncoding(){
ResponseEntity<String> response = restTemplate.exchange(
"https://www.url.com", HttpMethod.GET, new HttpEntity<Object>(requestHeaders),
String.class)
def gamesJson = JSON.parse(response.getBody())
//...
//parse value from gamesJson
//...
return testValue
}
Per my previous answer:
You just need to add the StringHttpMessageConverter to the template's message converters:
RestTemplate template = new RestTemplate();
template.getMessageConverters()
.add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
ResponseEntity<Object> response = template.exchange(endpoint, method, entity,
Object.class);
The encoding type can be enforced in the environment itself.
JAVA_TOOL_OPTIONS -Dfile.encoding=UTF8 -Dclient.encoding.override=UTF-8
Just try setting the above encoding settings in windows/linux. I hope this should resolve the issue.
In this case, JVM will pickup the default encoding type from environment variable.
Our team experienced a similar issue before, we have a 3rd party service and they said their output is encoded in UTF-8. But the strings returned are still garbled. After a bit of testing, it turns out that they were returning ISO-8859-1 encoded strings. What we did was to decode/encode their input into UTF-8 encoded characters so we can use those properly.
For your case, I think this is a similar issue:
UTF-8: Luleå
ISO-8859-1: Luleå
In Java, we did something like this:
Charset initialEncoding = Charsets.ISO_8859_1;
Charset outputEncoding = Charsets.UTF_8;
byte[] byteArray = input.getBytes(initialEncoding);
String output = new String(new String(byteArray, outputEncoding));
In Groovy, I think you could do something like
import groovy.json.JsonSlurper;
def main = {
def response = '{"name":"Luleå"}'
def slurper = new JsonSlurper()
def result = slurper.parse(response.getBytes(), 'UTF-8')
println result.name // prints Luleå
}
The answer to my problem is already found on Stack Exchange.
You just need to add the StringHttpMessageConverter to the template's message converters:
restTemplate.getMessageConverters()
.add(0, new StringHttpMessageConverter(Charset.forName("UTF-8")));
In my case that i had the same problem with contents received from my REST web service from the server and not my local enviroment.
I had search a lot and finally i found a solution that resolved my issue.
In Windows I added a new environment variable with key: JAVA_TOOL_OPTIONS and set its value to: -Dfile.encoding=UTF8.
The (Java) System property will be set automatically every time a JVM is started. You will know that the parameter has been picked up because the following message will be posted to System.err:
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF8

PUT Request not happening at all in Fantom

I am having some trouble with PUT requests to the google sheets api.
I have this code
spreadsheet_inputer := WebClient(`$google_sheet_URI_cells/R3C6?access_token=$accesstoken`)
xml_test := XDoc{
XElem("entry")
{
addAttr("xmlns","http://www.w3.org/2005/Atom")
addAttr("xmlns:gs","http://schemas.google.com/spreadsheets/2006")
XElem("id") { XText("https://spreadsheets.google.com/feeds/cells/$spreadsheet_id/1/private/full/R3C6?access_token=$accesstoken"), },
XElem("link") { addAttr("rel","edit");addAttr("type","application/atom+xml");addAttr("href","https://spreadsheets.google.com/feeds/cells/$spreadsheet_id/1/private/full/R3C6?access_token=$accesstoken"); },
XElem("gs:cell") { addAttr("row","3");addAttr("col","6");addAttr("inputValue","testing 123"); },
},
}
spreadsheet_inputer.reqHeaders["If-match"] = "*"
spreadsheet_inputer.reqHeaders["Content-Type"] = "application/atom+xml"
spreadsheet_inputer.reqMethod = "PUT"
spreadsheet_inputer.writeReq
spreadsheet_inputer.reqOut.writeXml(xml_test.writeToStr).close
echo(spreadsheet_inputer.resStr)
Right now it returns
sys::IOErr: No input stream for response 0
at the echo statement.
I have all the necessary data (at least i'm pretty sure) and it works here https://developers.google.com/oauthplayground/
Just to note, it does not accurately update the calendars.
EDIT: I had it return the response code and it was a 0, any pointers on what this means from the google sheets api? Or the fantom webclient?
WebClient.resCode is a non-nullable Int so it is 0 by default hence the problem would be either the request not being sent or the response not being read.
As you are obviously writing the request, the problem should the latter. Try calling WebClient.readRes() before resStr.
This readRes()
Read the response status line and response headers. This method may be called after the request has been written via writeReq and reqOut. Once this method completes the response status and headers are available. If there is a response body, it is available for reading via resIn. Throw IOErr if there is a network or protocol error. Return this.
Try this:
echo(spreadsheet_inputer.readRes.resStr)
I suspect the following line will also cause you problems:
spreadsheet_inputer.reqOut.writeXml(xml_test.writeToStr).close
becasue writeXml() escapes the string to be XML safe, whereas you'll want to just print the string. Try this:
spreadsheet_inputer.reqOut.writeChars(xml_test.writeToStr).close