Search code examples
multipartform-databox-apinock

npm nock: mock post multipart-form uploading file


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.


Solution

  • 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.