npm nock: mock post multipart-form uploading file - multipartform-data

Goal
With nock, I am seeking a solution in mocking an a tiny PNG file upload via POST multipart/form-data.
curl: Box API upload PNG file
Following curl script presents how to upload a file through Box API, file name: 'dummy.png' in root directory '0'.
curl 'https://upload.box.com/api/2.0/files/content' \
--request POST \
--verbose \
--silent \
--header 'authorization: Bearer [** Access Token **]' \
--header 'Content-Type: multipart/form-data' \
--form attributes='{ "name": "dummy.png", "parent": { "id": "0" } }' \
--form file=#'./files/dummy.png'
Condensed response:
Success [HTTP status: 201]
{
"total_count": 1,
"entries": [
{
"type": "file",
"name": "dummy.png",
"id": "584886508967"
}
]
}
nock attempt: Box API upload PNG file
The next code snippet is using npm nock works, however, this mocking is incomplete:
const accessToken = v4();
const randomFileId = v4();
let boundary = '';
const scope = nock('https://upload.box.com/api/2.0/')
.log((m, d) => logger.debug(m, d))
.matchHeader('authorization', `Bearer ${accessToken}`);
scope
.matchHeader('content-type', val => {
const matches = val.match(/^multipart\/form-data; boundary=([a-zA-Z0-9\-]+)$/);
if (matches && matches.length > 1) {
boundary = matches[1];
}
return !!matches;
})
.post('/files/content', body => {
return true;
})
.reply(201, {
entries: [
{
id: randomFileId,
name: 'dummy.png',
type: 'file'
}
]
});
nock attempt: Missing form attributes and file binary
It is not clear to me how to include with nock code what is included in curl POST request:
--header 'Content-Type: multipart/form-data' \
--form attributes='{ "name": "dummy.png", "parent": { "id": "0" } }' \
--form file=#'./files/dummy.png'
I would like to include in nock POST request:
File dummy.png binary as defined in --form file=#'./files/dummy.png'
File upload metadata as defined by --form attributes='{ "name": "dummy.png", "parent": { "id": "0" } }'
Thank you, appreciate the assistance.

As you alluded to in your question, Nock does not require the form data in order to intercept the request and mock the response. However, if you're testing that your code is sending the correct request body, doing the assertion is good practice.
The --form flag in cURL is a helper that does different thing for different protocols.
For HTTP protocol family, this lets curl emulate a filled-in form in which a user has pressed the submit button. This causes curl to POST data using the Content-Type multipart/form-data according to RFC 2388.
Not overly helpful, but the gist is that the data you're looking for in Nock will be in the body of the POST request. The Nock code in your question is on the right track. The post method using a callback as the second argument is how you can dig into the raw data being intercepted by Nock. The not-so-straight-forward part is that the body argument passed to the callback is hex encoded because the body includes the binary data of the png file.
.post('/files/content', body => {
const decoded = Buffer.from(body, 'hex');
console.log(decoded);
return true;
})
Adding the snippet above to your existing post method should output something similar to:
----------------------------493956036364114509087826
Content-Disposition: form-data; name="attributes"
{"name":"dummy.png","parent":{"id":"0"}}
----------------------------493956036364114509087826
Content-Disposition: form-data; name="content"; filename="unused"
Content-Type: image/png
�PNG
IHDRĉ
IDATx�c��������IEND�B`�
----------------------------493956036364114509087826--
It would be up to you at that point to determine if the body includes the data you expect and return true if so.
Referencing RFC 2388 for what multipart form-data should look like could help.

Related

Empty object being sent to DB

I'm writing a basic search function to query a postgresql db. When I'm running it and with console.logs up, I keep seeing that it's sending and returning a 200, however the object it is sending is blank. Below is my function followed by my curl command I'm sending. When I run the search function during the DB seed, it's able to find what I'm looking for.
curl http://localhost:5000/api/company/parts/GCS
companyRouter.get("/parts/:id", async (req, res, next) => {
const partNumber = req.body;
console.log("request 19", partNumber, req.body);
try {
const part = await searchPartsNumber(partNumber);
res.send({ part });
} catch ({ name, message }) {
next({ name, message });
}
});
SOLUTION -1:
you are not passing any body to the request . you are passing a url parameter. that's why your body is an empty object. if you want to get the parameter ( in your case GCS ) in your server you should do something like this
const partNumber = req.param('id');
SOLUTION - 2
otherwise if you want to pass body using curl you should modify your request like this.
curl --header "Content-Type: application/json" \
--request POST \
--data '{"partNumber":"YOUR_PART_NUMBER"}' \
http://localhost:5000/api/company/parts/GCS
and as req.body is an object. you should extract your partNumber like this
const {partNumber} = req.body;

Microsoft Azure - OAuth2 - "invalid_request"

I would like to connect my app with Microsoft Graph. I created my web-app in Azure (I have my client_id and client_secret). I am able to send a request to get the authorization code from https://login.microsoftonline.com/common/oauth2/v2.0/authorize.
The problem is that when I send a POST request in order to get the acess token from https://login.microsoftonline.com/common/oauth2/v2.0/token (exactly like said here in the "Using permissions" section) using Postman (with form-data option), I get an "AADSTS9000410: Malformed JSON" error:
{
"error": "invalid_request",
"error_description": "AADSTS9000410: Malformed JSON.\r\nTrace ID: f5c1dd4b-ad43-4265-91cb-1b7392360301\r\nCorrelation ID: 1dea54ed-bb43-4951-bc9e-001877fe427b\r\nTimestamp: 2019-01-14 21:38:42Z",
"error_codes": [9000410],
"timestamp": "2019-01-14 21:38:42Z",
"trace_id": "f5c1dd4b-ad43-4265-91cb-1b7392360401",
"correlation_id": "1dea54ed-bb43-4951-bc9e-001878fe427b"
}
Moreover, when I send the same request with a raw option in Postman, I get "AADSTS900144: The request body must contain the following parameter: 'grant_type'":
{
"error": "invalid_request",
"error_description": "AADSTS900144: The request body must contain the following parameter: 'grant_type'.\r\nTrace ID:a7c2f8f4-1510-42e6-b15e-b0df0865ff00\r\nCorrelation ID:e863cfa9-0bce-473c-bdf6-e48cfe2356e4\r\nTimestamp: 2019-01-1421:51:29Z",
"error_codes": [900144],
"timestamp": "2019-01-14 21:51:29Z",
"trace_id": "a7c2f8f4-1510-42e6-b15e-b0df0865ff10",
"correlation_id": "e863cfa9-0bce-473c-bdf6-e48cfe2356e3"
}
However, when I remove application/json in my header in Postman, and I put x-www-form-urlencoded option, everything looks fine.
I can only send POST requests with a JSON format in my application.
Does Microsoft Graph support JSON format for POST requests?
Is it a Postman issue?
I ran into a similar issue, but realized that there was a mismatch between the Content-Type: application/x-www-form-urlencoded header and the JSON-formatted request body. If you refer to this documentation, you'll see that the request body needs to be URL encoded (concatenated with ampersands, encoded entities, etc.), which ultimately resolved my issue. So, I don't believe this is an issue with Postman or MS APIs but, rather, just incorrect formatting of your request body.
I'm not sure what language your app uses, but here's an example using Node and Express that works for me:
const fetch = require('node-fetch')
const { URLSearchParams } = require('url')
async function getAccessToken(req, res, next) {
try {
const response = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
// Previously I was doing `body: JSON.stringify({...})`, but
// JSON !== URL encoded. Using `URLSearchParams` (or whatever
// the equivalent is in your language) is the key to success.
body: new URLSearchParams({
client_id: YOUR_CLIENT_ID_HERE,
scope: 'User.Read Calendars.Read',
redirect_uri: YOUR_REDIRECT_URL_HERE,
grant_type: 'authorization_code',
client_secret: YOUR_CLIENT_SECRET_HERE,
code: req.query.code
}
})
const json = await response.json()
// `json` will have `access_token` and other properties
} catch (err) {
throw err
}
}
Hope that helps!
It is neither a Microsoft nor a Postman issue, it is simply how OAuth defines the token workflow. This is defined in RFC 6749 - Section 4.1.3:
The client makes a request to the token endpoint by sending the following parameters using the application/x-www-form-urlencoded format per Appendix B with a character encoding of UTF-8 in the HTTP request entity-body

React Native fetch request to Microsoft Azure failing

I'm trying to use the Microsoft Azure OCR API found here for a React Native app.
I can get the API to work fine on local images with Postman, but for some reason, I get an "Unsupported Media Type" when I try using fetch within my app.
I originally called the api with this code:
_analyzeImage = () => {
const { image } = this.state;
const url = 'https://westcentralus.api.cognitive.microsoft.com/vision/v1.0/ocr';
const data = new FormData();
data.append(image);
fetch(url, {
method: 'post',
body: data,
headers: {
'Ocp-Apim-Subscription-Key': '***********************',
}
}).then(res => {
console.log(res)
});
}
Where image looks like:
That, when ran using the XCode simulator, yields:
And the response:
{
"code":"UnsupportedMediaType",
"requestId":"6ff43374-e5f9-4992-9657-82ec1e95b238",
"message": "Supported media types: application/octet-stream, multipart/form-data or application/json"
}
Weirdly, the content-type seemed to be test/plain. So, even though I thought that the FormData object was supposed to take care of content type, I tried adding 'content-type': 'multipart/form-data', but got the same response (although the content-type header in the network inspector did change to multipart/form-data.
I used create-react-native-app to set up the project, and want to to work on iOS and android. If anyone has any ideas - or any other ways to do OCR, if there's a better native solution - I'd appreciate it!
As stated in the doc page you link to, if you send
application/json, your payload must look like this:
{"url": "http://example.com/images/test.jpg"}
if application/octet-stream,
[Binary image data]
if multipart/form-data,
[Binary image data]
Right now you're not sending anything that matches expectations.
Example POST
The image,
Pass image by URL:
$ curl -v -X POST -H 'Ocp-Apim-Subscription-Key: 2exxxxxxxxxxxxxxxxxxxxxx' \
-H 'Content-type: application/json' \
--data-ascii '{ "url": "https://i.stack.imgur.com/RM7B3.png" }' \
https://westus.api.cognitive.microsoft.com/vision/v1.0/ocr
> POST /vision/v1.0/ocr HTTP/1.1
> Content-type: application/json
> Content-Length: 44
...
< HTTP/1.1 200 OK
< Content-Length: 196
< Content-Type: application/json; charset=utf-8
{
"language": "en",
...
"regions": [
{
...
"words": [
{
"boundingBox": "61,49,303,108",
"text": "hello."
}
...
or pass image by raw bytes:
$ curl -v -X POST -H 'Ocp-Apim-Subscription-Key: 2exxxxxxxxxxxxxxxxxxxxxx' \
-H 'Content-type: application/octet-stream' \
--data-binary #hello.png \
https://westus.api.cognitive.microsoft.com/vision/v1.0/ocr
> POST /vision/v1.0/ocr HTTP/1.1
> Content-type: application/octet-stream
> Content-Length: 11623
...
< HTTP/1.1 200 OK
< Content-Length: 196
< Content-Type: application/json; charset=utf-8
{
"language": "en",
...
"regions": [
{
...
"words": [
{
"boundingBox": "61,49,303,108",
"text": "hello."
}
...

File Upload Angular2 via multipart/form-data, 400 Error

I need to upload a file together with some meta info via multipart/form-data like this:
Header
----boundary
Meta
----boundary
File
----boundary
For some reason I always get a 400 Error, the Backend server tells me, that the Meta-Field is missing. However, if I look into the Hex/Ascii dump with Wireshark, the field is definitely there.
The following Curl-command works perfectly fine and the file gets uploaded successfully:
curl -H "Expect:" -v -i -X POST -H "Content-Type: multipart/form-data" -H "Authorization: "token" -F "File=#/location" -F 'Meta={"access":"token"}' http://path
Therefore this doesn't seem to be a Backend failure. My Angular (4.1.3) request must be bad, but I can't figure out what's wrong with it.
Template-Code:
<input #file_input type="file" (change)="onFileInputChange($event)" multiple>
Angular2-Code:
onFileInputChange(event) {
let fileList: FileList = event.target.files;
for (let i = 0; i < fileList.length; i++) {
let fp: File = fileList[i];
let formData: FormData = new FormData();
formData.append('File', fp, fp.name);
const body = JSON.stringify({
'access': "token"
});
this.formData.append('Meta', body);
let RequestHeader = new Headers();
// auto set content type
RequestHeader.append('Content-Type', '');
RequestHeader.append('Accept', 'application/json');
RequestHeader.append('Authorization', "token");
this.http.post(this.backend_url, formData, RequestHeader).map(
res => res.json()).subscribe(
data => console.log('success'),
error => console.log(error))
}
}
What am I missing here?
I finally found the solution.
The Content-Type header was malformed:
Content-Type: , multipart/form-data; boundary=----WebKit12345
The content type header in the actual code was preset.
By calling
headers.set('Content-Type', '');
I assumed, that the header content type was overwritten, but actually an empty string was prepended to it, so that there was a comma, which could not be parsed, of course.
I solved this by deleting the content type header completely:
headers.delete('Content-Type');

Multipart form parse error - Invalid boundary in multipart: None

I am very frustrated and could not find the soloution:
I am creating a project using angularjs and nodejs.I get image data from angular side in node js and send this data to further api.I got error
{
"error": {
"detail": "Multipart form parse error - Invalid boundary in multipart: None"
}
}
here is my code in nodejs side:
var request = require('request');
console.log(req.files);
var data = {
website:'www.gamail.xom',
contact_number:'dsdsd',
services_offered:'dsadasd',
contact_name:'dasdas',
provider_category:'exchange',
email:'kk#gmail.com',
image:req.files
}
var api_url = global.common.base_url + 'vcard/1.0.0/visit_card/' + req.param('uuid') +'/';
request({
url: api_url,
method: 'PUT',
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': 'Bearer '+req.cookies.apitoken
},
json: data
}, function(error, response, body) {
if(response.statusCode == 200 && !error){
res.end(JSON.stringify(body));
}else{
res.send(response.statusCode, { error: body });
}
});
}
In req.files i get
{ image:
[ { fieldName: 'image[0]',
originalFilename: 'icon_dd_chart_grey.png',
path: 'C:\\Users\\karakuma\\AppData\\Local\\Temp\\op74_gLSzPs-_49aT1GF0si
7.png',
headers: [Object],
size: 1474,
name: 'icon_dd_chart_grey.png',
type: 'image/png' } ] }
Try defining the content type as follows.
content_type='multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW'
I was facing the same issue and it worked for me in python.
I also faced this issue while trying to upload file. For me the issue was the FormData, which was coming as Object instead of FormData instance
So i converted my object to FormData using below code:
const getFormData = object => Object.keys(object).reduce((formData, key) => {
formData.append(key, object[key]);
return formData;
}, new FormData());
And the just executed my post request, in my case using Vue resource:
return Vue.http.post(url,
getFormData(formData),
{
headers: {
'Content-Type': 'multipart/form-data'
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
There is no need to mention the content type in header, fetch() will detect it's content type by itself.
let formData = new FormData()
formData.append("email", email);
formData.append("password", password);
formData.append("image", image);
const response = await fetch('http://localhost:8000/api/authentication/register/', {
method: 'POST',
headers: {'X-CSRFToken': csrftoken}, //django specific
body: formData,
});
I have been facing this problem in angular 11 connected to Django rest API, I was able to curl with something like this in order to upload a JSON with a form:
curl -X POST -S \
-H 'Accept: application/json' \
-u "user:password" \
-F "name=name" \
-F "label=mylabel" \
-F "otherfields=something" \
-F "photo=#./example.png;type=image/png" \
http://127.0.0.1:8000/api/v1/item/
But I was getting that exception using my header as httpOptions:
'content-type': 'multipart/form-data',
So I just commented out the content-type and it seems angular is so clever that he creates the header for you and will set the multipart together with the boundaries.
For more information on this:
What is the boundary in multipart/form-data?
A boundary is just the 'key' to separate the multiple "parts" of a multipart payload. Normally something like '&' is enough to separate the variables but you need something more unique to separate the payloads within the payload comment
You can use any value that not occurs in the data sent.
NOTE: Because boundary delimiters must not appear in the body parts being encapsulated, a user agent must exercise care to choose a unique boundary parameter value.
The simplest boundary delimiter line possible is something like "---", with a closing boundary delimiter line of "-----".

Resources