Search code examples
javascriptnode.jsfile-uploadmocha.jssupertest

How to simulate Client aborted


I am stuck in one of my tests. In my test, I need to detect that Client (browser) has cancelled or aborted image upload. My problem is how to simulate when the connection is aborted. In my controller, I have, so to speak simulated a connection loss, and my code reacts properly. But as I mentioned issue remains how to trigger that from the tests. I use Mocha 3.2.0 as a test Suite, and on the controller side I use multiparty.

This is the original code:

return new Promise((resolve, reject) => {
let form = new multiparty.Form();
let fileCount = 0;
form.on('part', function (part) {
    fileCount++;

    let tmpFile = path.join(os.tmpDir(), `${userId}_${timePrefix}_${path.basename(part.filename)}`);
    part.pipe(fs.createWriteStream(tmpFile));

    part.on('end', () => {
        resolve(fs.createReadStream(tmpFile));
    });

    part.on('error', function (error) {
        reject(error);
    });
});

form.on('error', (error) => {

  reject(new LogicalError(
      `Image input request is not valid`, errorCodes.VALIDATION.IMAGE_UPLOAD_DATA_INVALID);

});

form.parse(request);
}).then((imageStream) => {
//AWS S3 is hendled here...

Now I have added a new case, where I trigger that the client connection was cancelled. I have used Throttle. Throttle simulates slow upload, and in the timeout I trigger connection loss.

return new Promise((resolve, reject) => {
    let form = new multiparty.Form();
    let fileCount = 0;
    let throttle = new Throttle(1); //1 = 1 byte. 1000 = 1kb... all per second


    form.on('part', function (part) {
        fileCount++;

        let tmpFile = path.join(os.tmpDir(), `${userId}_${timePrefix}_${path.basename(part.filename)}`);
        //part.pipe(fs.createWriteStream(tmpFile));
        //Slows down the upload
        part.pipe(throttle).pipe(fs.createWriteStream(tmpFile));

        //trigger connection aborted
        setTimeout(function () {
            request.emit('aborted');
        },10);


        part.on('end', () => {
            resolve(fs.createReadStream(tmpFile));
        });

        part.on('error', function (error) {
            reject(error);
        });
    });

    form.on('error', (error) => {

        let err = null;

        if (error.message.includes('Request aborted')){
            err = new LogicalError(
                `Client aborted image upload process`, errorCodes.VALIDATION.IMAGE_UPLOAD_CLIENT_REQUEST_ABORTED);
        } else {
            err = new LogicalError(
                `Image input request is not valid`, errorCodes.VALIDATION.IMAGE_UPLOAD_DATA_INVALID);
        }
        reject(err);

    });

    form.parse(request);
}).then((imageStream) => {
//AWS S3 is hendled here...

This simulation works as expected. Code recognises proper condition (Request aborted). I can't leave this throttle and setTimeout, of course. How can I trigger it from the test?

This is the current test, that is uploading image, and targets the Controller. Using supertest:

describe('ImageControllerTest', function () {
    it('should upload image with name for the given user', (done) => {
        request(app)
            .put('path-to-controller/test-image.jpg')
            .attach('file', imagePath.testImagePath)
            .set(cons.TOKEN.HEADERS)
            .expect((res) => {
               //series of expect statements.
            })
            .expect(200, done);
    });
});

In my second attempt (code is above), I have simulated Connection Abort by doing this:

setTimeout(function () {
            request.emit('aborted');
        },10);

Multiparty listens to that event, and react accordingly.

How can I do it from the test?


Solution

  • The easy way can be - create client in subprocess using:

    const fork = require('child_process').fork;
    const path = require('path');
    const child = fork(path.join(__dirname, './some-sub.js'));
    

    And when you need to trigger cancel - just close the process:

    child.close();
    

    Or from the process itself:

    process.exit(0);