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
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?
@KeKs0r working example: https://gist.github.com/Couto/127ca8a6bd28ecc4a084
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.
Most helpful comment
here is example:
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.