Hapi: Question- How to write tests for file upload?

Created on 29 Mar 2014  路  11Comments  路  Source: hapijs/hapi

Hi,
I am trying to create a test for API call with mandatory file parameter. When I try to do this using the real API call using following form:

    <h2>Simple upload / POST</h2>
    <form action="http://localhost:8000/api/upload/simple" method="post"
enctype="multipart/form-data">
      Name: <input type="file" name="file" /> <br /> 
      <input type="submit" value="Call" />
    </form>

It is working as expected, but if I try to write test for that, it is not working.

I am using following route in (routes.attachHandlers(server)):

  server.route({
    method: 'POST',
    path: '/api/upload/simple',
    config: {
      validate : {
        payload: {file: Joi.object().required()}
      },
      handler: function(request, reply) {
        // Some upload functionality.
        reply('Done').type('text/plain');
      },
    }
  });

And here is the test I trying to be succesfull:

describe('simple upload', function() {
  var server = Hapi.createServer(0);
  before(function(done) {
    routes.attachHandlers(server);
    server.start(done);
  });

  it('successfull', function(done) {
    var payload = {
      file : fs.readFileSync('./test_file')
    };
    server.inject({url : '/api/upload/simple', method: 'post',  payload : payload}, function(res) {
      console.log(res);
      //expect(res.statusCode).to.equal(200);
      done();
    });
  });
});

In the response is filed following result:

{ statusCode: 400,
     error: 'Bad Request',
     message: 'the value of file must be an object',
     validation: { source: 'payload', keys: [] } } }

It seems to be problem with serve.inject, I am not sure how to pass testing file into the inject method. If I send there buffer, inject method will somehow alter that. What is the proper format of file, which should be passed to inject method?

thanks

support

Most helpful comment

here is example:

  it('upload succeds', function(done) {
    var payload = [
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="filename"',
      '',
      'CIMG3456.JPG',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="filesize"',
      '',
      '2897308',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="file"; filename="CIMG3456.JPG"',
      'Content-Type: image/jpeg',
      '',
      '',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi--'
    ];
    payload = payload.join('\r\n');
    var headers = {
      'content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryS4AeNzAzUP7OArMi'
    };
    server.inject({url : '/upload', method: 'post', payload : payload, headers: headers}, function(res) {
      expect(res.statusCode).to.equal(200);
      expect(res.result.success).to.equal(true);
      done();
    });
  });

My request was more complicated, I simplified during write of the post. I took original string from chrome from network tab (from headers of API request part Request Payload).

But still I am not sure how the real content of file should be sent. Now I got my tests running, the file will be created where it should be. There is just one thing - the content of it is broken. I am not sure how to send it.

All 11 comments

It's not a problem with server.inject - your route configuration is incorrect. If you're uploading a file, your config needs to include payload configuration that is not under validate.

config: {
    handler: function(request, reply) {
        // do something
      },
      payload: {
        maxBytes: 1048576 * 10, // 10MB
        output: 'stream',
        parse: false
     }
}

As far as testing POSTs with binary data, I don't think server.inject handles that. Try superagent?

@pity your inject call is missing the headers needed to know what file is being uploaded and how to process it. Make your request with curl -v and add the missing headers to the test.

@papajuans the payload defaults are fine, no need to add that.

@pity - Did you get this working? If you have an example I would love to see it. I'm having some trouble getting the headers correct with Server.inject

@peteotto look at the tests in tests/payload.js for some examples.

here is example:

  it('upload succeds', function(done) {
    var payload = [
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="filename"',
      '',
      'CIMG3456.JPG',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="filesize"',
      '',
      '2897308',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
      'Content-Disposition: form-data; name="file"; filename="CIMG3456.JPG"',
      'Content-Type: image/jpeg',
      '',
      '',
      '------WebKitFormBoundaryS4AeNzAzUP7OArMi--'
    ];
    payload = payload.join('\r\n');
    var headers = {
      'content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryS4AeNzAzUP7OArMi'
    };
    server.inject({url : '/upload', method: 'post', payload : payload, headers: headers}, function(res) {
      expect(res.statusCode).to.equal(200);
      expect(res.result.success).to.equal(true);
      done();
    });
  });

My request was more complicated, I simplified during write of the post. I took original string from chrome from network tab (from headers of API request part Request Payload).

But still I am not sure how the real content of file should be sent. Now I got my tests running, the file will be created where it should be. There is just one thing - the content of it is broken. I am not sure how to send it.

I've gotten the multi-part form created with the boundaries and I can send a text file across. However, when I create try to send a PDF using the following logic the file that ends up in the handler is corrupted. Am I doing something wrong with how I add the file to the payload?

var file = fs.readFileSync(pathToFile);
var payload = '--AaB03x\r\n' +
      'Content-Disposition: form-data; name="setting_id"\r\n' +
        '\r\n' +
        '201\r\n' +
        '--AaB03x\r\n' +
        'Content-Disposition: form-data; name="double_sided"\r\n' +
        '\r\n' +
        '1\r\n' +
        '--AaB03x\r\n' +
        'Content-Disposition: form-data; name="name"\r\n' +
        '\r\n' +
        'test\r\n' +
        '--AaB03x\r\n' +
        'Content-Disposition: form-data; name="file"; filename="4x6.pdf"\r\n' +
        'Content-Type: application/pdf\r\n' +
        '\r\n' +
        file + '\r\n' +
        '--AaB03x--\r\n';
var headers = {
    'Content-Type': 'multipart/form-data; boundary=AaB03x'
};

HTTP is limited character set and all binary content has to be encoded.

I already played a lot with encodings etc. I know that I sent the data binary from my frontend. But not sure how I can make the tests work:
This is my current approach:

        var filePath = __dirname + '/fixtures/sir.png';
        var file = fs.readFileSync(filePath);
        var payload = [
            '------WebKitFormBoundaryS4AeNzAzUP7OArMi',
            'Content-Disposition: form-data; name="file"; filename="CIMG3456.png"',
            'Content-Type: image/png',
            '',
            file,
            '------WebKitFormBoundaryS4AeNzAzUP7OArMi--'
        ];
        payload = payload.join('\r\n');
        var binaryPayload = new Buffer(payload, 'binary');

I know that my "file" is already a Buffer, but not sure in which encoding. So not sure what happens if I join it with my other strings. I also tried to convert it to utf8 and then back to binary. None of these approaches creates a valid image in my testcase. How do you guys decode your upload content?

@petermilan : you skipped the actual content of the file, how did u encode/decode in your testcase?

I got it to work this way:

`it('should return results in bounds', function(done) {
let form = new FormData();
const ne = {'latitude': 30.28051, 'longitude': -97.73246};
const sw = {'latitude': 30.22830, 'longitude': -97.77017};

    let stream = new Stream.Writable();
    stream.data = [];

    stream._write = function(chunk, encoding, callback) {
        this.data.push(chunk);
        callback();
    };

    stream.on('finish', function() {
        let buffer = Buffer.concat(this.data);

        const injectOptions = {
            method: 'POST',
            url: '/search-service/v0/search?ne=30.280514708088386,-97.7324666082859&sw=30.22830287490867,-97.77017880231143&pageSize=20&page=1',
            payload: buffer,
            headers: form.getHeaders()
        };
        return server.inject(injectOptions).then(function(response) {
            expect(response.statusCode).to.equal(200);
            done();
        });
    });

    form.append('bitmap', fs.createReadStream(path.resolve('tests/unit/server/routes/integration/bitmap.png')));
    form.pipe(stream);
});`

This thread has been automatically locked due to inactivity. Please open a new issue for related bugs or questions following the new issue template instructions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mateeyow picture mateeyow  路  5Comments

arb picture arb  路  4Comments

jeffbski picture jeffbski  路  5Comments

taoeffect picture taoeffect  路  3Comments

leore picture leore  路  3Comments