Supertest: Random test success/failure on Windows

Created on 18 May 2015  路  5Comments  路  Source: visionmedia/supertest

I test this API:

function handleError(res){
    return function(data) {
        res.status(data.status).json(data.error);
    };
}

function reject(deferred, error, statusCode) {
    deferred.reject({
        status: statusCode || error.status || 500,
        error: error.payload
    });
}

function responseWithResult(res){
    return function(data) {
        if (data.entity) {
            return res.status(data.statusCode).json(data.entity);
        }
        res.status(500).json(error.UNKNOWN_ERROR.payload);
    };
}

function resolve(deferred, entity, statusCode) {
    deferred.resolve({
        statusCode: statusCode || 200,
        entity: entity
    });
}

function validate(deferred, filename, mimetype) {
    if (/.(?:jpe?g|png|gif)$/.test(filename.toLowerCase()) === false) {
    // Should reply here with status code 400 and body: { "code": 90, "message": "The file extension is not supported. Accepted extensions: jpg, png and gif." }
        reject(deferred, error.FILE_EXTENSION_NOT_SUPPORTED, 400); 
        return false;
    }
    if (/^image\/.+$/.test(mimetype) === false) {
        reject(deferred, error.WRONG_MIME_TYPE, 400);
        return false;
    }
    return true;
}

exports.create = function(req, res) {
    var deferredResponse = Q.defer();
    var image;

    deferredResponse.promise
        .then(responseWithResult(res))
        .catch(handleError(res));

    var form = new busboy({
        headers: req.headers,
        limits: {
            fields: 1,
            parts: 2,
            files: 1,
            fileSize: 5 * 1024 * 1024
        }
    })
    .on('file', function(fieldname, file, filename, encoding, mimetype) {
        if (validate(deferredResponse, filename, mimetype)) {
            image = uniqueFilename(path.extname(filename));
            file.pipe(fs.createWriteStream(path.join(IMAGES_FOLDER, image)));
        }
        file.on('data', function(data) {});
        file.on('end', function() {
            if (file.truncated) {
                reject(deferredResponse, error.FILE_SIZE_LIMIT_EXCEEDED);
            }
        });
    })
    .on('error', function() {
        reject(deferredResponse, error.BAD_PARSING);
    })
    .on('finish', function() {
        resolve(deferredResponse, { filename: image }, 201);
    });
    req.pipe(form);
};

With this test:

it('should respond with with 400 and the error code 90 on unsupported file extension', function(done) {
    request(app)
        .post('/api/images')
        .set('authorization', 'Bearer ' + token)
        .attach('file', path.join(TEST_FOLDER, 'test.tiff'))
        .expect(400)
        .expect('Content-Type', /json/)
        .end(function(err, res) {
            if (err) return done(err);
            expect(res.body.code).to.equal(90);
            done();
        });
});

It always success on MacOS and TravisCI but when I run it on Windows, it fails most of time (6/10 times) and returns the error:

  1) Image API: POST /api/images should respond with with 400 and the error code 90 on unsupported file extension:
     Uncaught TypeError: Cannot read property 'header' of undefined
      at Test.assert (<project_root>\node_modules\supertest\lib\test.js:188:17)
      at Server.assert (<projet_root>\node_modules\supertest\lib\test.js:131:12)
      at Server.g (events.js:199:16)
      at Server.emit (events.js:104:17)
      at net.js:1409:10
      at process._tickDomainCallback (node.js:381:11)

The API works as expected while uploading a tiff image in Chrome:
image

Details:

  • Node v0.12.2
  • Windows 8.1 x64
  • grunt-mocha-test 0.12.7
  • supertest 1.0.1
  • chai-as-promised 5.0.0
  • chai-things 0.2.0
  • sinon-chai 2.7.0

I've never seen anything like that before. The random behavior with the same code is really weird.

Most helpful comment

I figured it out. I just added the "Connection: keep-alive" header in the request. If someone has an explanation, I would be happy to know it!

it('should respond with with 400 and the error code 90 on unsupported file extension', function(done) {
    request(app)
        .post('/api/images')
        .set('authorization', 'Bearer ' + token)
        .set('Connection', 'keep-alive')
        .attach('file', path.join(TEST_FOLDER, 'test.tiff'))
        .expect(400)
        .expect('Content-Type', /json/)
        .end(function(err, res) {
            if (err) return done(err);
            expect(res.body.code).to.equal(90);
            done();
        });
});

All 5 comments

I figured it out. I just added the "Connection: keep-alive" header in the request. If someone has an explanation, I would be happy to know it!

it('should respond with with 400 and the error code 90 on unsupported file extension', function(done) {
    request(app)
        .post('/api/images')
        .set('authorization', 'Bearer ' + token)
        .set('Connection', 'keep-alive')
        .attach('file', path.join(TEST_FOLDER, 'test.tiff'))
        .expect(400)
        .expect('Content-Type', /json/)
        .end(function(err, res) {
            if (err) return done(err);
            expect(res.body.code).to.equal(90);
            done();
        });
});

I'm having a similar issue, and the suggested fix won't work for me. If I keep the connection open, my test will timeout. I've verified that this is only an issue on Windows.

API method:

exports.changeProfilePicture = function (req, res) {
  var user = req.user;
  var message = null;
  var upload = multer(config.uploads.profileUpload).single('newProfilePicture');
  var profileUploadFileFilter = require(path.resolve('./config/lib/multer')).profileUploadFileFilter;

  // Filtering to upload only images
  upload.fileFilter = profileUploadFileFilter;

  if (user) {
    upload(req, res, function (uploadError) {
      if(uploadError) {
        return res.status(400).send({
          message: 'Error occurred while uploading profile picture'
        });
      } else {
        user.profileImageURL = config.uploads.profileUpload.dest + req.file.filename;

        user.save(function (saveError) {
          if (saveError) {
            return res.status(400).send({
              message: errorHandler.getErrorMessage(saveError)
            });
          } else {
            req.login(user, function (err) {
              if (err) {
                res.status(400).send(err);
              } else {
                res.json(user);
              }
            });
          }
        });
      }
    });
  } else {
    res.status(400).send({ // I have verified that the test makes it here every time
      message: 'User is not signed in'
    });
  }
};

Test:

it('should not be able to change profile picture if not signed in', function (done) {
    agent.post('/api/users/picture')
      .attach('newProfilePicture', './modules/users/client/img/profile/default.png')
      .send(credentials)
      .expect(400)
      .end(function (userInfoErr, userInfoRes) {
        done(userInfoErr);
      });
  });

This is the error I receive, about 60% of the time when running this test..

 1) User CRUD tests should not be able to change profile picture if not signed in:
    TypeError: Cannot read property 'status' of undefined
     at Test._assertStatus (project\node_modules\supertest\lib\test.js:229:10)
     at Test._assertFunction (project\node_modules\supertest\lib\test.js:247:11)
     at Test.assert (project\node_modules\supertest\lib\test.js:148:18)
     at Server.assert (project\node_modules\supertest\lib\test.js:127:12)
     at Server.g (events.js:199:16)
     at Server.emit (events.js:129:20)
     at net.js:1419:10
     at process._tickDomainCallback (node.js:381:11)

All my other tests run fine. I have two other tests that implement .attach on the request, but this is the only one that has an issue.

The other tests that never produce this error:

it('should not be able to change profile picture if attach a picture with a different field name', function (done) {
    agent.post('/api/auth/signin')
      .send(credentials)
      .expect(200)
      .end(function (signinErr, signinRes) {
        // Handle signin error
        if (signinErr) {
          return done(signinErr);
        }

        agent.post('/api/users/picture')
          .attach('fieldThatDoesntWork', './modules/users/client/img/profile/default.png')
          .send(credentials)
          .expect(400)
          .end(function (userInfoErr, userInfoRes) {
            done(userInfoErr);
          });
      });
  });

it('should be able to change profile picture if signed in', function (done) {
    agent.post('/api/auth/signin')
      .send(credentials)
      .expect(200)
      .end(function (signinErr, signinRes) {
        // Handle signin error
        if (signinErr) {
          return done(signinErr);
        }

        agent.post('/api/users/picture')
          .attach('newProfilePicture', './modules/users/client/img/profile/default.png')
          .send(credentials)
          .expect(200)
          .end(function (userInfoErr, userInfoRes) {
            // Handle change profile picture error
            if (userInfoErr) {
              return done(userInfoErr);
            }

            userInfoRes.body.should.be.instanceof(Object);
            userInfoRes.body.profileImageURL.should.be.a.String();
            userInfoRes.body._id.should.be.equal(String(user._id));

            return done();
          });
      });
  });

I had the same exact issue, but with a different root cause I believe.
I was using malformed paths like for instance api/auth/signin/ instead of /api/auth/signin.
As soon as I switched to the second format tests ran fine.
Hope this helps.

I have this same issue, but neither solution works for me :(

Same with @timendez, neither solution works for me. I have no idea why the crud test Random success/failure on docker Jenkins. The _post_ and _put_ request often fail. The error log is

08:18:25   2) EmployeeScore API
08:18:25        GET /employee_scores/:id
08:18:25          should return employeeScore:
08:18:25      TypeError: Cannot read property 'header' of undefined
08:18:25       at Test._assertHeader (node_modules/supertest/lib/test.js:232:20)
08:18:25       at Test._assertFunction (node_modules/supertest/lib/test.js:281:11)
08:18:25       at Test.assert (node_modules/supertest/lib/test.js:171:18)
08:18:25       at assert (node_modules/supertest/lib/test.js:131:12)
08:18:25       at /var/jenkins_home/workspace/employee_scores/employee_scores/node_modules/supertest/lib/test.js:128:5
08:18:25       at Test.Request.callback (node_modules/superagent/lib/node/index.js:718:3)
08:18:25       at ClientRequest.req.once.err (node_modules/superagent/lib/node/index.js:646:10)
08:18:25       at Socket.socketErrorListener (_http_client.js:395:9)
08:18:25       at emitErrorNT (internal/streams/destroy.js:64:8)
08:18:25       at process._tickCallback (internal/process/next_tick.js:178:19)
08:18:25 
08:18:25   3) EmployeeScore API
08:18:25        DELETE /employee_scores/:id
08:18:25          should return employeeScore:
08:18:25      TypeError: Cannot read property 'header' of undefined
08:18:25       at Test._assertHeader (node_modules/supertest/lib/test.js:232:20)
08:18:25       at Test._assertFunction (node_modules/supertest/lib/test.js:281:11)
08:18:25       at Test.assert (node_modules/supertest/lib/test.js:171:18)
08:18:25       at assert (node_modules/supertest/lib/test.js:131:12)
08:18:25       at /var/jenkins_home/workspace/employee_scores/employee_scores/node_modules/supertest/lib/test.js:128:5
08:18:25       at Test.Request.callback (node_modules/superagent/lib/node/index.js:718:3)
08:18:25       at ClientRequest.req.once.err (node_modules/superagent/lib/node/index.js:646:10)
08:18:25       at Socket.socketErrorListener (_http_client.js:395:9)
08:18:25       at emitErrorNT (internal/streams/destroy.js:64:8)
08:18:25       at process._tickCallback (internal/process/next_tick.js:178:19)
Was this page helpful?
0 / 5 - 0 ratings

Related issues

EverettQuebral picture EverettQuebral  路  3Comments

hacker0limbo picture hacker0limbo  路  3Comments

ryami333 picture ryami333  路  4Comments

nareshbhatia picture nareshbhatia  路  6Comments

rkmax picture rkmax  路  5Comments