Spring-test cannot set #RequestPart. It always send it as null - spring-mvc

I cannot test Controller by using mockMvc in spring-test. I want to know right way to test API with #RequestPart.
The method to test is this.
#RequestMapping(value = "/", method = RequestMethod.POST)
public ResponseEntity<Object> replaceFile(
#RequestPart("files") Map<String, Object> files,
#RequestPart("fileKey") String fileKey)
And to test I build a mock request like below.
MockMultipartFile blob = new MockMultipartFile("files", files.getBytes());
MockMultipartFile key = new MockMultipartFile("fileKey", fileKey.getBytes());
mockMvc.perform(fileUpload("/")
.file(blob)
.file(key))
.andDo(print())
.andExpect(status().isOk());
As you can see, I used fileUpload. But in past time, I tried to use post with content or requestAttr because all of them didn't work.
I think current code is the closest to answer among I tried, but can't get closer anymore.
The weird thing is, the real in-use API is almost same with them.
In client side, user sent a new FormData() object to request and server can get data properly.
The server side code is below.
#RequestMapping(value = "/api/{variable}", method = RequestMethod.POST)
public ResponseEntity<Object> apiMethod(
#PathVariable int variable,
#RequestPart("dto1") DTO1 dto1,
#RequestPart("dto2") DTO2 dto2,
#RequestPart("file") Map<String, Object> file)
"file" part consists of "file name" key and its blob value encoded base64.
For example, {"hello.txt": "SGVsbG8gd29ybGQh"}
What I want to know - Right way to test API with #RequestPart
What I tried
method - fileUpload / data - file, content, requestAttr but they send null.
method - post / data - file, content, requestAttr but they throw MultipartException.
Restriction - Cannot use multipart because the system is using a low version of Spring.
Thanks!

Thanks to #borino who commented to my question, I got the clue of problem.
It is certain to use fileUpload for testing API with #RequestPart arguments.
In that case, it caused from content type.
I declared MockMultipartFile without contentType.
MockMultipartFile blob = new MockMultipartFile("files", files.getBytes());
MockMultipartFile key = new MockMultipartFile("fileKey", fileKey.getBytes());
But the arguments of API have a type, Map<String,Object> and String each.
As #borino said to me, I changed constructor of MockMultipartFile to make sure contentType, and it works!
MockMultipartFile blob = new MockMultipartFile("files", "", "application/json", files.getBytes());
MockMultipartFile key = new MockMultipartFile("fileKey", "", "application/json", fileKey.getBytes());
Just add contentType when you have a problem like me. Thanks!

Related

POST method to upload file to Azure storage - what to return

I am creating an app where
user can upload the text file and then
find most used word and change that word in text and
show the changed text to the user.
if it is possible, I would like to
get the file’s text content before uploading when Post method is being called and save that content
so I add the “DownloadTextAsync()” method inside of the POST method, but it seems like I am calling this method to the wrong subject?
[HttpPost("UploadText")]
public async Task<IActionResult> Post(List<IFormFile> files)
{
string connectionString = Environment.GetEnvironmentVariable("mykeystringhere");
// Create a BlobServiceClient object which will be used to create a container client
BlobServiceClient blobServiceClient = new BlobServiceClient(connectionString);
//Create a unique name for the container
string containerName = "textdata" + Guid.NewGuid().ToString();
// Create the container and return a container client object
BlobContainerClient containerClient = await blobServiceClient.CreateBlobContainerAsync(containerName);
// Create a local file in the ./data/ directory for uploading and downloading
string localPath = "./data/";
string fileName = "textfiledata" + Guid.NewGuid().ToString() + ".txt";
string localFilePath = Path.Combine(localPath, fileName);
// Get a reference to a blob
BlobClient blobClient = containerClient.GetBlobClient(fileName);
// Open the file and upload its data
using FileStream uploadFileStream = System.IO.File.OpenRead(localFilePath);
await blobClient.UploadAsync(uploadFileStream, true);
uploadFileStream.Close();
string downloadFilePath = localFilePath.Replace(".txt", "DOWNLOAD.txt");
// Get the blob file as text
string contents = blobClient.DownloadTextAsync().Result;
//return the string
return contents;
//if (uploadSuccess)
// return View("UploadSuccess");
//else
// return View("UploadError");
}
The issues I am having are
I understood that ‘blobClient’ is the reference to the blob, where I can get the file’s data but this must be wrong?
Also it seems like I cannot use “CloudBlobContainer” nor the “CloudBlockBlob blob”. Is it because inside of the POST method, the blob has been just initialized and does not exist when these twos are executed?
Also when I test the POST method, the console throws “Refused to load the font '' because it violates the following Content Security Policy directive: "default-src 'none'". Note that 'font-src' was not explicitly set, so 'default-src' is used as a fallback.” which I googled but have no idea what it means?
I have tried different ways but keep getting CANNOT POST/“ But could not really find the solid anwers. Could this be related to my POST method?
I understood that ‘blobClient’ is the reference to the blob, where I
can get the file’s data but this must be wrong?
That's correct in a sense that you can use blobClient to perform operations on blob like upload/download etc. I am not sure why you say but this must be wrong.
Also it seems like I cannot use “CloudBlobContainer” nor the
“CloudBlockBlob blob”. Is it because inside of the POST method, the
blob has been just initialized and does not exist when these twos are
executed?
No, this is happening because you're using a newer version of SDK (version 12.x.x) and CloudBlobContainer and CloudBlockBlob are available in the older version of the SDK.
Also when I test the POST method, the console throws “Refused to load
the font '' because it violates the following Content Security Policy
directive: "default-src 'none'". Note that 'font-src' was not
explicitly set, so 'default-src' is used as a fallback.” which I
googled but have no idea what it means? I have tried different ways
but keep getting CANNOT POST/“ But could not really find the solid
anwers. Could this be related to my POST method?
Not sure why this is happening. You may want to ask a separate question for this and when you do, please include the HTML portion of your code as well.

How can I catch a pound sign(#) from URL with multiple values in ASHX files

This is my ASHX File where I am catching multiple parameters from request URL using httpcontext, and it is working properly but when I am including a Hash(#) value in Text parameter through the URL. It is not taking the value of FLOW which is another parameter(next to Text parameter).
So it is working for:
http://localhost:10326/ussd.ashx?user=MSL&pass=MSL663055&tid=65506&msisdn=8801520101525&text=***3333**&flow=begin&momt=mo
And it is not working for:
http://localhost:10326/ussd.ashx?user=MSL&pass=MSL663055&tid=65506&msisdn=8801520101525&text=***3333#**&flow=begin&momt=mo
My ASHX files:
public void ProcessRequest(HttpContext context)
{
HttpRequest httpRequest = context.Request;
string user = httpRequest.QueryString["user"].ToString();
string pass = httpRequest.QueryString["pass"].ToString();
string tid = httpRequest.QueryString["tid"].ToString();
string msisdn = httpRequest.QueryString["msisdn"].ToString();
string text = httpRequest.QueryString["text"].ToString();
flow = httpRequest.QueryString["flow"].ToString();
HttpContext.Current.Session["user"] = user;
HttpContext.Current.Session["pass"] = pass;
HttpContext.Current.Session["tid"] = tid;
HttpContext.Current.Session["msisdn"] = msisdn;
HttpContext.Current.Session["text"] = text;
HttpContext.Current.Session["flow"] = flow;
You need to URI encode your parameter values before they are added to the URL. This way the server will not get confused by unsafe characters such as '#' which has its own meaning when included as part of a URL. See RFC 3986 Section 2.
See Encode URL in JavaScript as an example of how to encode the sent data using JavaScript. Whatever is sending the data in the URL will need to do the encoding. There is not much you can do once the request has reached the server. Without knowing how your URL is being created, I can't offer much more.
In short: the problem is with your client code not your ASHX file.

Illegal argument exception: Host name may not be null

Illegal Argument exception: Host name may not be null
I am getting this error at last line
HttpResponse httpResponse = httpClient.execute(get)
I tried all possible solutions like encoding url if contains space etc.. and variables like name and phone all these are from my calling class
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss.SSS");
String time = sdf.format(cal.getTime());
String value="Dish:"+cr.getString(1)+"Quantity:"+cr.getInt(2)+"Price"+cr.getString(3).trim()+"TotalPrice:"+Integer.parseInt(cr.getString(3))*cr.getInt(2)+"Address:"+address+"CustomerName:"+name+"RestaurantName:"+cr.getString(4).trim();
url = "http:twowaits.in/orderapp.php?name="+name.trim()+"&no="+phone.trim()+"&add="+URLEncoder.encode(address, "UTF-8")+"&rest="+URLEncoder.encode(cr.getString(4),"UTF-8")+"&cost="+cr.getString(3).trim()+"&value="+URLEncoder.encode(value, "UTF-8")+"&dishname="+cr.getString(1).trim()+"&qty="+cr.getInt(2)+"&time="+time;
HttpGet get = new HttpGet(url);
DefaultHttpClient httpClient = new DefaultHttpClient();
HttpResponse httpResponse = httpClient.execute(get);
You forgot the // after http:.
url = "http://twowaits.in/......
The stacktrace says it all; your URL does not contain a hostname (a domain name or IP). That means you either didn't supply one, or you made a formatting error somewhere so the URL couldn't be parsed properly. In this case, you did supply a domain name, it's just that the URL isn't formatted properly.
Note that the Apache HttpClient that you are using is deprecated and Google recommends you switch to something else, e.g. URLConnection. Square's OKHttp is also a great alternative.
Also, you might want to try and make your code more readable. Using a builder pattern for your URI would probably help a lot. See URIBuilder or you could just use a StringBuilder.

CreateEnvelopeFromTemplates - Guid should contain 32 digits with 4 dashes

I am attempting to create a DocuSign envelope from a template document using the CreateEnvelopeFromTemplates method, available within their v3 SOAP API web service. This is being instantiated from a asp.NET v4.0 web site.
Upon calling the method armed with the required parameter objects being passed in. I am recieving an exception from the web service, basically telling me that the Template ID is not a valid GUID.
669393: Guid should contain 32 digits with 4 dashes (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx).
Line 14889:
Line 14890: public DocuSignDSAPI.EnvelopeStatus CreateEnvelopeFromTemplates(DocuSignDSAPI.TemplateReference[] TemplateReferences, DocuSignDSAPI.Recipient[] Recipients, DocuSignDSAPI.EnvelopeInformation EnvelopeInformation, bool ActivateEnvelope) {
Line 14891: return base.Channel.CreateEnvelopeFromTemplates(TemplateReferences, Recipients, EnvelopeInformation, ActivateEnvelope);
Line 14892: }
Line 14893:
The template reference, a guid. Must be specified as the "Template" string property against TemplateReference object. This is then added to a dynamic array of TemplateReferences, which is one of the input parameters of the CreateEnvelopeFromTemplates method.
Actual template GUID: f37b4d64-54e3-4723-a6f1-a4120f0e9695
I am building up my template reference object using the following function that i wrote to try and make the functionality reusable:
Private Function GetTemplateReference(ByVal TemplateID As String) As TemplateReference
Dim templateReference As New TemplateReference
Dim guidTemplateID As Guid
With TemplateReference
.TemplateLocation = TemplateLocationCode.Server
If Guid.TryParse(TemplateID, guidTemplateID) Then
.Template = guidTemplateID.ToString
End If
End With
Return TemplateReference
End Function
The TemplateID is being passed in from a appSetting configuration value at the time of the TemplateReferences array instantiation like so...
templateReferences = New TemplateReference() {GetTemplateReference(ConfigurationManager.AppSettings("DocuSignTemplate_Reference"))}
recipients = New Recipient() {AddRecipient("myself#work.email", "My Name")}
envelopeInformation = CreateEnvelopeInformation()
envelopeStatus = client.CreateEnvelopeFromTemplates(templateReferences, recipients, envelopeInformation, True)
As you can see from my GetTemplateReference function I am also parsing the GUID before setting it back as a string so i know its valid. The template is managed and stored at the DocuSign end, hence specifying the document location.
I am referring to their own documentation:
CreateEnvelopeFromTemplates
Why oh why is the method not liking my Template ID? I can successfully use their REST API to call the same method, using their own code samples. Worst case I can make use of this but would rather interact with the web service as I would need to construct all the relevent requests in either XML or JSON.
I would really appreciate if someone could perhaps shed some light on this problem.
Thanks for taking the time to read my question!
Andrew might be spot on with the AccountId mention - are you setting the AccountId in the envelope information object? Also, have you seen the DocuSign SOAP SDK up on Github? That has 5 sample SOAP projects including one MS.NET project. The .NET project is in C# not Visual Basic, but still I think it will be helpful to you. Check out the SOAP SDK here:
https://github.com/docusign/DocuSign-eSignature-SDK
For instance, here is the test function for the CreateEnvelopeFromTemplates() function:
public void CreateEnvelopeFromTemplatesTest()
{
// Construct all the recipient information
DocuSignWeb.Recipient[] recipients = HeartbeatTests.CreateOneSigner();
DocuSignWeb.TemplateReferenceRoleAssignment[] finalRoleAssignments = new DocuSignWeb.TemplateReferenceRoleAssignment[1];
finalRoleAssignments[0] = new DocuSignWeb.TemplateReferenceRoleAssignment();
finalRoleAssignments[0].RoleName = recipients[0].RoleName;
finalRoleAssignments[0].RecipientID = recipients[0].ID;
// Use a server-side template -- you could make more than one of these
DocuSignWeb.TemplateReference templateReference = new DocuSignWeb.TemplateReference();
templateReference.TemplateLocation = DocuSignWeb.TemplateLocationCode.Server;
// TODO: replace with template ID from your account
templateReference.Template = "server template ID";
templateReference.RoleAssignments = finalRoleAssignments;
// Construct the envelope information
DocuSignWeb.EnvelopeInformation envelopeInfo = new DocuSignWeb.EnvelopeInformation();
envelopeInfo.AccountId = _accountId;
envelopeInfo.Subject = "create envelope from templates test";
envelopeInfo.EmailBlurb = "testing docusign creation services";
// Create draft with all the template information
DocuSignWeb.EnvelopeStatus status = _apiClient.CreateEnvelopeFromTemplates(new DocuSignWeb.TemplateReference[] { templateReference },
recipients, envelopeInfo, false);
// Confirm that the envelope has been assigned an ID
Assert.IsNotNullOrEmpty(status.EnvelopeID);
Console.WriteLine("Status for envelope {0} is {1}", status.EnvelopeID, status.Status);
}
This code calls other sample functions in the SDK which I have not included, but hopefully this helps shed some light on what you're doing wrong...
This problem arises when you don't set up the field AccountId. This field can be retrieved from your account. In Docusign's console go to Preferences / API and look here
Where to find AccountID Guid in Docusign's Console
Use API Account ID (which is in GUID format) and you should be OK.

How do I set the response format to the same format as the request?

I created a custom dispatcher to handle versioning that uses a customer media type. It looks something like this:
application/vnd.mycompany.myapi-v1+json
The extraction of the version number in order to select the correct controller is all up and working, but being new to MVC, I am not sure how to set the response format. What we want to do is set the response format to match the request. So in this example, the response will be in json. Now I assume I'm going to have to extract that from this content type as well which is fine, but could someone give me an example of how i set the response format of this request in MVC4 assuming I have already created the method which will extract the format as a string?
private string GetResponseFormat(){
//some shennanigans here
}
P.S. the reason for not having the client use the accept header during the request is that there are already clients out there that are using our old service which would set the accept header to match the request.
You can also use Content method to return custom response type:
string responseType = GetResponseFormat();
...
switch(responseType){
case "json":
string json = "yourJSON";
return Content(json, "application/json");
case "xml":
string xml = "yourXML";
return Content(xml, "text/xml");
default:
string plaintxt = "yourPlaintext";
return Content(plaintxt, "text/plain"):
}
I was able to clear the existing Accept header and add to it:
private void SetResponseFormatToRequestFormat(HttpRequestMessage request)
{
// figure out what the request format was
_contentTypeHeader = request.Content.Headers.ContentType.ToString();
if(_contentTypeHeader.Contains("xml")) _contentType = "application/xml";
if (_contentTypeHeader.Contains("json")) _contentType = "application/json";
// set response format to the same as the request format
request.Headers.Accept.Clear();
request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue(_contentType));
}

Resources