4

I'm trying to write at test for a Sails.Js controller action that downloads a user's avatar image. The controller action looks like this:

/**
* Download avatar of the user with the specified id
*
* (GET /user/:id/avatar)
*/
avatar: function (req, res) {

  req.validate({
    id: 'string'
  });

  User.findOne(req.param('id')).exec(function (err, user){
        if (err) return res.negotiate(err);
        if (!user) return res.notFound();

        // User has no avatar image uploaded.
        // (should have never have hit this endpoint and used the default image)
        if (!user.avatarFd) {
          return res.notFound();
        }

        var SkipperDisk = require('skipper-disk');
        var fileAdapter = SkipperDisk(/* optional opts */);

        // Stream the file down
        fileAdapter.read(user.avatarFd)
        .on('error', function (err){
          return res.serverError(err);
        })
        .pipe(res);
    });
}

So far the test looks like this:

describe('try to download a user avatar', function() {
    var result;
    it('should return 200', function(done) {
        server
            .get('/user/' + testUser.id + '/avatar')
            .expect(200)
            .end(function(err, res) {
                if (err) return done(err);
                result = res.body;
                return done();
            });
        }
    }
    it('should return binary data stream', function(done) {
      // make some assertions.
    });
});

I want to add another test to make sure that what has been returned is binary data, but I can't figure out how this would be done. Anyone know the right way to go about this?

UPDATE

After attempting a solution in the mode that @sgress454 suggested below, I ended up with this test:

tmp = require('temporary'); 
  // https://github.com/vesln/temporary

// Write nonsense bytes to a file fixture.
var EOF = '\x04';
var size = 1000;
var fileFixture = new tmp.File();
fileFixture.writeFileSync(crypto.pseudoRandomBytes(size) + EOF);
fileFixture.size = size;

after(function() {
fileFixture.unlinkSync();
});

describe('try to download a user avatar', function() {
    var download;
    it('should return 200', function(done) {
        server
            .get('/user/' + testUser.id + '/avatar')
            .expect(200)
            .end(function(err, res) {
                if (err) return done(err);
                download = new Buffer(res.text, 'ascii');
                return done();
            });
        });
    it('should return binary stream', function(done) {
        var testAvatar = fs.readFileSync(fileFixture.path);
        download.toString('base64').should.be.equal(testAvatar.toString('base64'));
        done(); 
    });
});

So this test mocks up a file using temporary. The trouble is that when I compare the result I get back from the server and the mock file that I'm reading from the mocked file system, they aren't the same. I get the following as expected:

  +bEk277+9azo277+916jvv71g77+9KO+/vV/vv71577+977+9U8e9A++/ve+/vSgiF++/ve+/vWsi77+977+9BRRs77+977+977+9bO+/vSoGRW3vv73vv73dr++/ve+/vXEAIFbvv70p77+977+9WMuSSm/vv73vv71W77+977+9LULvv70J77+9eFfSoVsW77+977+9QAleLgDvv71T77+9de+/vRHvv71qyJPvv73vv73vv73vv73vv71S77+91L4sf++/vQHaiicDKXXvv71977+9NO+/vUzvv71YXe+/vTjvv70n77+9fWvvv73vv709YgoW77+9bmF/77+9JNK4LO+/vUNdNGjvv70TZMazS+2IjBdgL++/ve+/vRXvv71S77+977+9SHHvv70QY++/vSbvv70SC2US77+9eGnvv71cczVOFBp7fu+/ve+/ve+/ve+/vWTvv70B77+9cm/vv73vv73vv73vv70q77+977+9JSxY77+9TO+/vQbvv73vv71sREl+Qyrvv70JFXgSHBLvv71v77+977+9AkBPOwvvv73vv73vv71R77+9VSHvv71DZ2NB77+977+977+977+9Pu+/ve+/vcabMe+/ve+/ve+/ve+/vUFnXFJP77+977+977+977+9G1/vv73vv73vv71OQe+/ve+/vdmU77+9B++/vUts77+9Zu+/vS9uUH3vv73vv73vv71y77+9PlRXSSJ3UHHvv73vv71SBXvvv73vv70677+977+9dk5O77+9de+/vTzvv70Y77+9cmjvv73vv73vv73vv712UNC0WW7vv73vv71lZD4+77+9U++/vR4MNW8RY37vv70ZTUo2fl0sKu+/vUN/bipyPO+/vSrvv73vv73vv700Bjwa77+977+9RH8A77+977+977+9zrDvv73vv70JQ2tOKe+/vV7Mk2Hvv73vv73vv70L77+9Tu+/vQwPK++/ve+/ve+/vVTvv73vv70M77+977+9Zs2/Vu+/vXzvv73vv73vv71a77+977+977+9Au+/vSrvv73vv70S77+977+9eO+/ve+/vVFk77+977+9Jm4L77+977+9fVnRl05x77+9ai1SSDZiX2fvv73vv73vv73vv73vv73vv73vv73vv71Y77+977+977+9VFvvv71B77+9X++/vTbvv70w77+977+9TO+/vSQBMB4+77+977+9Z++/vTDvv73vv71/77+977+9Dnd9Be+/vUZCehYuFu+/vVfvv73vv73vv73vv73vv73vv70+HO+/ve+/ve+/ve+/ve+/vSgLVitLQO+/ve+/vUZP77+977+977+9adWy77+977+9H++/ve+/vWTvv71677+93Zzvv73vv73vv71t77+977+977+9BGMkIFkYSxRW77+977+977+9Ke+/vRoN77+9f9CIUXQXWu+/vSYp77+9VDPvv71fLAxU77+977+977+9N++/vTbvv73vv73vv71dIjzvv73vv73vv71Z77+977+9He+/ve+/vWd6LO+/vQDvv70Bae+/vRQZ77+977+90YLvv717Ji3vv716Bu+/ve+/ve+/vVpU77+9aO+/ve+/vWnvv73vv70u2a/vv73vv73vv71p77+9WiAh77+9JyLvv73vv73vv73vv71QIUzvv71pypRO77+9Fe+/vQ7vv70Z77+9Se+/vUHvv73vv73vv70tA++/vSjvv73vv73vv73vv716K8e677+977+977+977+9Zyjvv73vv71U77+9Oe+/vRcF77+9Ku+/ve+/ve+/ve+/vVl777+9ewUAUu+/ve+/ve+/vUV/GGA6fu+/ve+/vVfvv705BA50D++/vSrvv73vv73vv71d77+977+977+977+9KO+/ve+/vUBzbO+/ve+/ve+/ve+/vXUnPS7vv71gCe+/vQ/vv70d77+9P00d77+9Tx8cOz8ABe+/vRbvv70t77+9IO+/ve+/ve+/ve+/ve+/ve+/vSQt77+9GE7vv73vv73vv73vv73So++/vVTvv71BEgDvv73vv70BdRYeTO+/vTjvv71+Ku+/vXjTu++/ve+/ve+/vRQK77+9Su+/vTvskJB/b1dyU++/ve+/vW7vv71k77+9Pu+/ve+/ve+/ve+/ve+/vVk277+9Pyfvv73vv73vv70mXO+/ve+/ve+/ve+/ve+/vQIr77+9QO+/vS1nAyXvv73vv713Ve+/vVTvv70VcV5m77+9M++/ve+/ve+/vWUx77+9OT1g77+9MQnvv71N77+977+977+9byjvv73vv71W77+977+9x5rvv70PBO+/ve+/ve+/ve+/ve+/ve+/ve+/vQd0Ru+/ve+/vU1zG++/vW5W77+977+9ES9udy3vv71CbGpVDgXvv71977+977+9QhLvv71xfnEN77+9KzDvv70KKO+/vVDvv70E

And the following as the actual response:

  -bEk2/Ws6Nv3o/WD9KP1f/Xn9/VP9A/39KCIX/f1rIv39BRRs/f39bP0qBkVt/f1v/f1xACBW/Sn9/VjSSm/9/Vb9/S1C/Qn9eFehWxb9/UAJXi4A/VP9df0R/WoT/f39/f1S/T4sf/0BiicDKXX9ff00/Uz9WF39OP0n/X1r/f09YgoW/W5hf/0kuCz9Q100aP0TZLNLDBdgL/39Ff1S/f1Icf0QY/0m/RILZRL9eGn9XHM1ThQae379/f39ZP0B/XJv/f39/Sr9/SUsWP1M/Qb9/WxESX5DKv0JFXgSHBL9b/39AkBPOwv9/f1R/VUh/UNnY0H9/f39Pv39mzH9/f39QWdcUk/9/f39G1/9/f1OQf39VP0H/Uts/Wb9L25Qff39/XL9PlRXSSJ3UHH9/VIFe/39Ov39dk5O/XX9PP0Y/XJo/f39/XZQNFlu/f1lZD4+/VP9Hgw1bxFjfv0ZTUo2fl0sKv1Df24qcjz9Kv39/TQGPBr9/UR/AP39/bD9/QlDa04p/V4TYf39/Qv9Tv0MDyv9/f1U/f0M/f1mf1b9fP39/Vr9/f0C/Sr9/RL9/Xj9/VFk/f0mbgv9/X1ZV05x/WotUkg2Yl9n/f39/f39/f1Y/f39VFv9Qf1f/Tb9MP39TP0kATAePv39Z/0w/f1//f0Od30F/UZCehYuFv1X/f39/f39Phz9/f39/SgLVitLQP39Rk/9/f1pcv39H/39ZP16/Vz9/f1t/f39BGMkIFkYSxRW/f39Kf0aDf1/CFF0F1r9Jin9VDP9XywMVP39/Tf9Nv39/V0iPP39/Vn9/R39/Wd6LP0A/QFp/RQZ/f1C/XsmLf16Bv39/VpU/Wj9/Wn9/S5v/f39af1aICH9JyL9/f39UCFM/WmUTv0V/Q79Gf1J/UH9/f0tA/0o/f39/Xor+v39/f1nKP39VP05/RcF/Sr9/f39WXv9ewUAUv39/UV/GGA6fv39V/05BA50D/0q/f39Xf39/f0o/f1Ac2z9/f39dSc9Lv1gCf0P/R39P00d/U8fHDs/AAX9Fv0t/SD9/f39/f0kLf0YTv39/f2j/VT9QRIA/f0BdRYeTP04/X4q/Xj7/f39FAr9Sv07EH9vV3JT/f1u/WT9Pv39/f39WTb9Pyf9/f0mXP39/f39Aiv9QP0tZwMl/f13Vf1U/RVxXmb9M/39/WUx/Tk9YP0xCf1N/f39byj9/Vb9/dr9DwT9/f39/f39B3RG/f1Ncxv9blb9/REvbnct/UJsalUOBf19/f1CEv1xfnEN/Ssw/Qoo/VD9BA==

I'm not sure why the files would be different at this point. Perhaps the problem is in the way I'm parsing the data that comes back?

fraxture
  • 5,113
  • 4
  • 43
  • 83

1 Answers1

6

This is one of those cases where the most obvious solution is the best: use the controller action to download a known file in your test, then load that same file from disk within the test and compare it to the one that was downloaded. You'll save an avatar file in your test directory somewhere (perhaps under a fixtures subdirectory), and make sure that before your test runs, a user is created whose avatarFd points to that file. Then, the easiest (least efficient) way to do the second test is to just leave your first test as-is, and re-run the request:

it('should return binary data stream', function(done) {
    server
      .get('/user/' + testUser.id + '/avatar')
      .end(function(err, res) {
          if (err) return done(err);
          result = res.text;
          var testAvatar = require('fs').readFileSync(pathToTestAvatar);
          assert.equal(testAvatar.toString(), result.toString();
          return done();
      });
});

Note the reference to res.text instead of res.body--since you're not specifying a content-type header for the response in your controller, Supertest makes no assumptions and just adds the raw data to the text field in the response. If you put a res.set("content-type", "image/jpeg"); in the controller, for example, then the response body in your test would be a Buffer with the image bytes in it.

When I want to run separate tests on the result of a request, I tend to do the request itself in a before function, then save the body and statusCode in closure-scoped variables as you started to do here. Then you can run individual it tests for things like the status code, body contents, etc. So my whole test would look something like:

var assert = require('assert');
var path = require('path');
var server = require('supertest');
var fs = require('fs');

describe('try to download a user avatar', function() {
    var result, status;
    var testAvatarFd = path.resolve(__dirname, '..', 'fixtures', 'test.jpeg');

    // Create the user and make the request before running the actual tests.
    before(function(done) {
      // Create a test user
      User.create({
        avatarFd: testAvatarFd
      }).exec(function(err, testUser) {
        if (err) {return done(err);}

          // Then request the user avatar from the server
          server(sails.hooks.http.app)
          .get('/user/' + testUser.id + '/avatar')
          .end(function(err, res) {
              if (err) return done(err);
              // Save the result in our closure-scoped variables
              result = res.text;
              status = res.statusCode;
              return done();
          });
      });
    });

    // The status code test is now synchronous
    it('should return 200', function() {
      assert.equal(status, 200);
    });

    // As is the file test, since we're using `readFileSync`
    it('should return binary data stream', function() {
      var testAvatar = fs.readFileSync(testAvatarFd);
      assert.equal(testAvatar.toString(), result.toString());
    });
});
sgress454
  • 24,870
  • 4
  • 74
  • 92
  • Thank you @sgress454 for this comment. You've gotten me most of the way toward a solution I think. But in `res.text` I'm getting some gobbledy gook that I should probably be able to identity. I will update my question. – fraxture Aug 27 '15 at 08:33