orbotix/sphero.js

View on GitHub
spec/lib/packet.spec.js

Summary

Maintainability
F
3 days
Test Coverage
"use strict";

var Packet = lib("packet"),
    utils = lib("utils");

describe("Packet", function() {
  var packet;

  beforeEach(function() {
    packet = new Packet({emitPacketErrors: true});
  });

  describe("#constructor", function() {
    it("generates a Sphero packet representation", function() {
      expect(packet).to.be.an.instanceOf(Packet);
      expect(packet.partialBuffer).to.be.an.instanceOf(Buffer);
      expect(packet.partialCounter).to.be.eql(0);
    });
  });

  describe("#create", function() {
    var buffer, opts;

    beforeEach(function() {
      opts = {
        sop2: 0xFE,
        did: 0x01,
        cid: 0x02,
        seq: 0x03,
        data: [0x04, 0x05, 0x06, 0x07, 0x08]
      };

      buffer = packet.create(opts);
    });

    it("turns packet obj representation into a byte array", function() {
      expect(buffer).to.be.an.instanceOf(Array);
    });

    it("sets 1st byte of the array (SOP1) the value passed", function() {
      opts.sop1 = 0xF0;
      buffer = packet.create(opts);
      expect(buffer[0]).to.be.eql(0xF0);
    });

    it("sets 2nd byte of the array (SOP2) to 0xFE", function() {
      expect(buffer[1]).to.be.eql(0xFE);
    });

    it("sets 3rd byte of the array (DID) to 0x01", function() {
      expect(buffer[2]).to.be.eql(0x01);
    });

    it("sets 4th byte of the array (CID) to 0x02", function() {
      expect(buffer[3]).to.be.eql(0x02);
    });

    it("sets 5th byte of the array (SEQ) to 0x03", function() {
      expect(buffer[4]).to.be.eql(0x03);
    });

    it("sets 6th byte of the array (DLEN) to 0x03", function() {
      expect(buffer[5]).to.be.eql(0x06);
    });

    it("sets the checksum (last byte) of the array to 0xD5", function() {
      expect(buffer[buffer.length - 1]).to.be.eql(0xD5);
    });

    context("when packet opts not specified", function() {
      beforeEach(function() {
        buffer = packet.create();
      });

      it("sets 1st byte of the array (SOP1) to 0xFF", function() {
        expect(buffer[0]).to.be.eql(0xFF);
      });

      it("sets 2nd byte of the array (SOP2) defult value", function() {
        expect(buffer[1]).to.be.eql(0xFF);
      });

      it("sets 3rd byte of the array (DID) to 0x01", function() {
        expect(buffer[2]).to.be.eql(0x00);
      });

      it("sets 4th byte of the array (CID) to 0x02", function() {
        expect(buffer[3]).to.be.eql(0x01);
      });

      it("sets 5th byte of the array (SEQ) to 0x03", function() {
        expect(buffer[4]).to.be.eql(0x00);
      });

      it("sets data of the array (data) to []", function() {
        expect(buffer[5]).to.be.eql(1);
      });
    });
  });

  describe("#parse", function() {
    context("with sync response", function() {
      var buffer, res, data;

      beforeEach(function() {
        data = [0x05, 0x04, 0x03, 0x02, 0x01];
        buffer = new Buffer([0xFF, 0xFF, 0x00, 0x02, 0x06].concat(data, 0xE8));

        res = packet.parse(buffer);
      });

      it("turns a sphero buffer response into a response obj", function() {
        expect(res).to.be.an.instanceOf(Object);
      });

      it("res@sop1 should be 0xFF", function() {
        expect(res.sop1).to.be.eql(0xFF);
      });

      it("res@sop2 should be 0xFF", function() {
        expect(res.sop2).to.be.eql(0xFF);
      });

      it("res@mrsp should be 0x00", function() {
        expect(res.mrsp).to.be.eql(0x00);
      });

      it("res@seq should be 0x02", function() {
        expect(res.seq).to.be.eql(0x02);
      });

      it("res@dlen should be 0x06", function() {
        expect(res.dlen).to.be.eql(0x06);
      });

      it("res@data should be a buffer 6 bytes long", function() {
        expect(res.data).to.be.an.instanceOf(Buffer);
        expect(res.data.length).to.be.eql(res.dlen - 1);
      });

      it("res@data should be eql", function() {
        var tmpBuffer = new Buffer(data);
        expect(res.data).to.be.eql(tmpBuffer);
      });

      it("res@checksum should be 0xFE", function() {
        expect(res.checksum).to.be.eql(0xE8);
      });

      context(" when checksum is incorrect", function() {
        beforeEach(function() {
          var tmpBuffer = [0xFF, 0xFF, 0x00, 0x02, 0x06];

          data = [0x05, 0x04, 0x03, 0x02, 0x01];
          buffer = new Buffer(tmpBuffer.concat(data, 0xEE));

          stub(packet, "emit");

          packet.emitPacketErrors = true;
          res = packet.parse(buffer);
        });

        afterEach(function() {
          packet.emit.restore;
        });

        it("emits an error event with a checksum Error param", function() {
          expect(packet.emit).to.be.calledOnce;
          expect(packet.emit)
            .to.be.calledWith(
              "error",
              new Error("Incorrect checksum, packet discarded")
            );
        });

        it("@partialBuffer should be empty", function() {
          expect(packet.partialBuffer.length).to.be.eql(0);
        });

        it("res should be null", function() {
          expect(res).to.be.null;
        });
      });

      context("buffer length is less than minSizeReq", function() {
        beforeEach(function() {
          buffer = new Buffer([0xFF, 0xFF, 0x00, 0x02]);

          res = packet.parse(buffer);
        });

        it("partialBuffer should not be empty", function() {
          expect(packet.partialBuffer.length).to.be.eql(4);
        });

        it("res should be null", function() {
          expect(res).to.be.null;
        });
      });

      context("buffer length is less than expectedSize", function() {
        beforeEach(function() {
          buffer = new Buffer([0xFF, 0xFF, 0x00, 0x02, 0x06, 0x01, 0x02]);

          res = packet.parse(buffer);
        });

        it("partialBuffer should not be empty", function() {
          expect(packet.partialBuffer.length).to.be.eql(7);
        });

        it("res should be null", function() {
          expect(res).to.be.null;
        });
      });

      context("buffer length is greater than expectedSize", function() {
        beforeEach(function() {
          buffer = new Buffer(
            [0xFF, 0xFF, 0x00, 0x02, 0x01, 0xFC, 0xFF, 0xFF, 0x00]
          );

          res = packet.parse(buffer);
        });

        it("partialBuffer should not be empty", function() {
          expect(packet.partialBuffer.length).to.be.eql(3);
        });

        it("partialBuffer should be eql to", function() {
          var tmpBuffer = new Buffer([0xFF, 0xFF, 0x00]);
          expect(packet.partialBuffer).to.be.eql(tmpBuffer);
        });

        it("res should be a packet obj", function() {
          expect(res).to.not.be.null;
          expect(res).to.be.eql({
            sop1: 0xFF,
            sop2: 0xFF,
            mrsp: 0x00,
            seq: 0x02,
            dlen: 0x01,
            data: new Buffer(0),
            checksum: 0xFC,
          });
        });
      });

      context("SOPs don't pass validation", function() {
        beforeEach(function() {
          buffer = new Buffer([0xF0, 0x00, 0x02, 0x01].concat(0xFC));
        });

        context("and @partialBuffer is empty", function() {
          beforeEach(function() {
            res = packet.parse(buffer);
          });

          it("partialBuffer should not be empty", function() {
            expect(packet.partialBuffer.length).to.be.eql(5);
          });

          it("res should be null", function() {
            expect(res).to.be.null;
          });
        });

        context("and @partialBuffer is NOT empty", function() {
          beforeEach(function() {
            packet.partialBuffer = new Buffer([0xFF]);
            res = packet.parse(buffer);
          });

          it("partialBuffer should be empty", function() {
            expect(packet.partialBuffer.length).to.be.eql(0);
          });

          it("res should be null", function() {
            expect(res).to.be.null;
          });
        });
      });

      context("when partialResponse is not empty", function() {
        beforeEach(function() {
          buffer = new Buffer([0xFF, 0x00, 0x02, 0x01].concat(0xFC));
          packet.partialBuffer = new Buffer([0xFF]);

          res = packet.parse(buffer);
        });

        it("returns a packet obj when calling parse", function() {
          expect(res).to.not.be.null;
          expect(res).to.be.eql({
            sop1: 0xFF,
            sop2: 0xFF,
            mrsp: 0x00,
            seq: 0x02,
            dlen: 0x01,
            data: new Buffer(0),
            checksum: 0xFC,
          });
        });

        it("packet@partialBuffer is empty", function() {
          expect(packet.partialBuffer.length).to.be.eql(0);
        });
      });
    });

    context("sync response", function() {
      var buffer, res, data;

      beforeEach(function() {
        data = [0x05, 0x04, 0x03, 0x02, 0x01];
        buffer = new Buffer([0xFF, 0xFE, 0x0A, 0x00, 0x06].concat(data, 0xE0));

        res = packet.parse(buffer);
      });

      it("turns a sphero buffer response into a response obj", function() {
        expect(res).to.be.an.instanceOf(Object);
      });

      it("packet res@idCode should be 0x0A", function() {
        expect(res.idCode).to.be.eql(0x0A);
      });

      it("packet res@dlenMsb should be 0x00", function() {
        expect(res.dlenMsb).to.be.eql(0x00);
      });

      it("packet res@dlenLsb should be 0x06", function() {
        expect(res.dlenLsb).to.be.eql(0x06);
      });

      it("packet res@dlen should be 0x06", function() {
        expect(res.dlen).to.be.eql(0x06);
      });

      it("packet res@checksum should be 0xFE", function() {
        expect(res.checksum).to.be.eql(0xE0);
      });
    });
  });

  describe("#parseResponseData", function() {
    var payload;

    beforeEach(function() {
      payload = {
        sop1: 0xFF,
        sop2: 0xFF,
        mrsp: 0x00,
        seq: 0x02,
        dlen: 0x01,
        data: new Buffer(0),
        checksum: 0xFC,
      };

      stub(packet, "_parseData");
      packet._parseData.returns({ val1: "uno" });

      packet.parseResponseData({ did: 0x02, cid: 0x07 }, payload);
    });

    it("returns payload if cmd is not valid", function() {
      var res = packet.parseResponseData({}, payload);
      expect(res).to.be.eql(payload);
    });

    it("calls #_parseData with params", function() {
      var parser = {
        desc: "Get Chassis Id",
        did: 2,
        cid: 7,
        event: "chassisId",
        fields: [{ name: "chassisId", type: "number" }]
      };
      expect(packet._parseData).to.be.calledWith(parser, payload);
    });
  });

  describe("#_parseData", function() {
    var parser, payload;

    beforeEach(function() {
      payload = {
        sop1: 0xFF,
        sop2: 0xFF,
        mrsp: 0x00,
        seq: 0x02,
        dlen: 0x01,
        data: new Buffer([0xff]),
        checksum: 0xFC,
      };

      parser = {
        desc: "Get Chassis Id",
        did: 2,
        cid: 7,
        event: "chassisId",
        fields: [{ name: "chassisId", type: "number" }]
      };

      stub(packet, "_checkDSMasks");
    });

    afterEach(function() {
      packet._checkDSMasks.restore();
    });

    context("when dsMasks return -1", function() {
      beforeEach(function() {
        packet._checkDSMasks.returns(-1);
        packet._parseData(parser, payload);
      });

      it("calls #_checkDSMasks once", function() {
        expect(packet._checkDSMasks).to.be.calledOnce;
      });

      it("returns payload inmmediately", function() {
        expect(packet._parseData(parser, payload)).to.be.eql(payload);
      });
    });

    context("when dsBit returs 1", function() {
      beforeEach(function() {
        stub(packet, "_checkDSBit");
        stub(packet, "_parseField");

        packet._checkDSMasks.returns(0);
        packet._checkDSBit.returns(1);
        packet._parseField.returns(255);

        packet._parseData(parser, payload, { did: 0x02, cid: 0x07 });
      });

      afterEach(function() {
        packet._checkDSBit.restore();
        packet._parseField.restore();
      });

      it("calls #_checkDSBit once", function() {
        expect(packet._checkDSBit).to.be.calledOnce;
      });

      it("calls #_parseField with params", function() {
        var field = parser.fields[0];
        field.from = 0;
        field.to = 2;
        expect(packet._parseField).to.be.calledOnce;
      });
    });

    context("when dsBit returs 0", function() {
      beforeEach(function() {
        stub(packet, "_incParserIndex");
        stub(packet, "_checkDSBit");
        stub(packet, "_parseField");

        packet._checkDSMasks.returns(0);
        packet._incParserIndex.returns(1);
        packet._checkDSBit.returns(0);
        packet._parseField.returns(255);

        packet._parseData(parser, payload, { did: 0x02, cid: 0x07 });
      });

      afterEach(function() {
        packet._incParserIndex.restore();
        packet._checkDSBit.restore();
        packet._parseField.restore();
      });

      it("calls #_incParserIndex once with params", function() {
        expect(packet._incParserIndex).to.be.calledOnce;
        expect(packet._incParserIndex)
          .to.be.calledWith(0, parser.fields, payload.data, 0, 0);
      });
    });

    context("parser is null or data length is 0", function() {
      beforeEach(function() {
        payload.data = new Buffer(0);
        spy(packet, "_parseData");
        packet._parseData(null, payload, { did: 0x02, cid: 0x07 });
      });

      it("calls #_incParserIndex once with params", function() {
        expect(packet._parseData).returned(payload);
      });
    });
  });

  describe("#CheckdsMasks", function() {
    beforeEach(function() {
      spy(packet, "_checkDSMasks");
    });

    afterEach(function() {
      packet._checkDSMasks.restore();
    });

    it("returns null when idCode !== 0x03", function() {
      packet._checkDSMasks({}, { idCode: 0x07 });
      expect(packet._checkDSMasks).to.have.returned(null);
    });

    it("returns ds obj when ds is valid and idCode == 0x03", function() {
      var ds = { mask1: 0xFF00, mask2: 0x00FF };
      packet._checkDSMasks(ds, { idCode: 0x03 });
      expect(packet._checkDSMasks).to.have.returned(ds);
    });

    it("returns -1 when ds is invalid and idCode == 0x03", function() {
      var ds = { mask1: 0xFF00 };
      packet._checkDSMasks(ds, { idCode: 0x03 });
      expect(packet._checkDSMasks).to.have.returned(-1);
    });
  });

  describe("#_incParserIndex", function() {
    beforeEach(function() {
      spy(packet, "_incParserIndex");
    });

    afterEach(function() {
      packet._incParserIndex.restore();
    });

    it("returns i++ with dsFlag < 0", function() {
      packet._incParserIndex(0, [], [], -1, 0);
      expect(packet._incParserIndex).to.have.returned(1);
    });

    it("returns i++ with i < fields.length", function() {
      packet._incParserIndex(0, [1, 2, 3], [4, 5, 6]);
      expect(packet._incParserIndex).to.have.returned(1);
    });

    it("returns i++ with dsIndex = data.length", function() {
      packet._incParserIndex(0, [1, 2, 3], [4, 5, 6, 7], 0, 4);
      expect(packet._incParserIndex).to.have.returned(1);
    });

    it("returns i = 0 when all conditions met", function() {
      packet._incParserIndex(3, [1, 2, 3, 4], [4, 5, 6, 7], 0, 2);
      expect(packet._incParserIndex).to.have.returned(0);
    });
  });

  describe("checker", function() {
    it("#_checksum should return 0xFC", function() {
      var buffer = [0xFF, 0xFF, 0x00, 0x02, 0x01, 0xFC],
          check = utils.checksum(buffer.slice(3, 5));
      expect(check).to.be.eql(0xFC);
    });

    it("#_checkSOPs with SOP2 0xFF should return 'sync'", function() {
      var buffer = [0xFF, 0xFF, 0x00, 0x02, 0x01, 0xFC],
          check = packet._checkSOPs(buffer);
      expect(check).to.be.eql("sync");
    });

    it("#_checkSOPs with SOP2 0xFE should return 'async'", function() {
      var buffer = [0xFF, 0xFE, 0x00, 0x02, 0x01, 0xFC],
          check = packet._checkSOPs(buffer);
      expect(check).to.be.eql("async");
    });

    it("#_checkSOPs with SOP2 0xFE should return 'async'", function() {
      var buffer = [0xFF, 0xFC, 0x00, 0x02, 0x01, 0xFC],
          check = packet._checkSOPs(buffer);
      expect(check).to.be.eql(false);
    });

    it("#_checkExpectedSize should return 6 when size == expected", function() {
      var buffer = [0xFF, 0xFF, 0x00, 0x02, 0x01, 0xFC],
          check = packet._checkExpectedSize(buffer);
      expect(check).to.be.eql(6);
    });

    it("#_checkExpectedSize should return -1 when size < expected", function() {
      var buffer = [0xFF, 0xFC, 0x00, 0x02, 0x04, 0x02, 0x03],
          check = packet._checkExpectedSize(buffer);
      expect(check).to.be.eql(-1);
    });

    it("#_checkMinSize should return true when size >= min", function() {
      var buffer = [0xFF, 0xFF, 0x00, 0x02, 0x01, 0xFC],
          check = packet._checkMinSize(buffer);
      expect(check).to.be.eql(true);
    });

    it("#_checkMinSize should return false when size < min", function() {
      var buffer = [0xFF, 0xFC, 0x00, 0x02, 0x01],
          check = packet._checkMinSize(buffer);
      expect(check).to.be.eql(false);
    });
  });

  describe("#checkDSBit", function() {
    beforeEach(function() {
      spy(packet, "_checkDSBit");
    });

    afterEach(function() {
      packet._checkDSBit.restore();
    });

    it("returns -1 when DS is invalid", function() {
      packet._checkDSBit(null);
      expect(packet._checkDSBit).to.have.returned(-1);
    });

    it("returns 1 when DS valid and field in mask1|2", function() {
      packet._checkDSBit(
        { mask1: 0xFFFF },
        { bitmask: 0x1000, maskField: "mask1"
      });
      expect(packet._checkDSBit).to.have.returned(1);
    });

    it("returns 0 when DS valid and field not in mask1|2", function() {
      packet._checkDSBit(
        { mask1: 0x0FFF },
        { bitmask: 0x1000, maskField: "mask1"
      });
      expect(packet._checkDSBit).to.have.returned(0);
    });
  });

  describe("#_parseField", function() {
    var data, field;

    beforeEach(function() {
      field = {
        name: "chassisId",
        type: "number",
      };

      data = {
        slice: stub()
      };

      data.slice.returns(new Buffer([0xFF, 0xFE, 0x00, 0x01]));
      spy(utils, "bufferToInt");

      spy(packet, "_parseField");

      packet._parseField(field, data);
    });

    afterEach(function() {
      utils.bufferToInt.restore();
    });

    it("calls data#slice", function() {
      expect(data.slice).to.be.calledOnce;
      expect(data.slice).to.be.calledWith(undefined, undefined);
    });

    it("calls utils#bufferToInt", function() {
      expect(utils.bufferToInt).to.be.calledOnce;
    });

    context("when field type is: ", function() {
      beforeEach(function() {
        data.slice.reset();
        utils.bufferToInt.reset();
        packet._parseField.reset();
        packet._parseField(field, [255]);
      });

      it("'number' returns the value", function() {
        expect(packet._parseField).to.have.returned(255);
      });

      it("'signed' returns the signed value", function() {
        field.type = "signed";
        field.from = 0;
        field.to = 1;
        var tmpVal = packet._parseField(field, new Buffer([0xFF, 0xFE, 0x00, 0x01]));
        expect(tmpVal).to.be.eql(-1);
      });

      it("'number' returns a hex string when format == 'hex'", function() {
        field.format = "hex";
        packet._parseField(field, [255]);
        expect(packet._parseField).to.have.returned("0xFF");
        field.format = undefined;
      });

      it("'string' returns a string with format == 'ascii'", function() {
        field.type = "string";
        field.format = "ascii";
        packet._parseField(field, new Buffer([0x48, 0x6F, 0x6C, 0x61, 0x21]));
        expect(packet._parseField).to.have.returned("Hola!");
        field.format = undefined;
      });

      it("'raw' returns the raw array", function() {
        var buffer = new Buffer([0x48, 0x6F, 0x6C, 0x61, 0x21]);
        field.type = "raw";
        var tmpVal = packet._parseField(field, buffer);
        expect(tmpVal).to.be.eql(buffer);
      });

      it("'predefined' returns 'battery OK'", function() {
        var buffer = new Buffer([0x02]);
        field.type = "predefined";
        field.values = { 0x02: "battery OK" };
        packet._parseField(field, buffer);
        expect(packet._parseField).to.have.returned("battery OK");
        field.values = undefined;
      });

      it("'predefined' returns 'true' with mask", function() {
        var buffer = new Buffer([0x0F]);
        field.type = "predefined";
        field.mask = "0x01";
        field.values = { 0x01: true };
        packet._parseField(field, buffer);
        expect(packet._parseField).to.have.returned(true);
        field.values = undefined;
      });

      it("'bitmask' calls #_parseBotmaskField", function() {
        stub(packet, "_parseBitmaskField");
        packet._parseBitmaskField.returns({ val: "all Good" });
        field.type = "bitmask";
        packet._parseField(field, [0x01, 0x02], { val1: "one" });
        expect(packet._parseBitmaskField).to.be.calledOnce;
        expect(packet._parseBitmaskField)
          .to.be.calledWith(
            258, field, { val1: "one" }
          );
        packet._parseBitmaskField.restore();
      });

      it("'bitmask' calls #_parseBotmaskField", function() {
        field.type = "thevoid";
        stub(packet, "emit");
        packet._parseField(field, [0x01, 0x02]);
        expect(packet.emit).to.be.calledOnce;
        var error = new Error("Data could not be parsed!");
        expect(packet.emit).to.be.calledWith("error", error);
        expect(packet._parseField)
          .to.have.returned("Data could not be parsed!");
      });
    });
  });

  describe("#_parseBitmaskField", function() {
    var field;

    beforeEach(function() {
      field = {
        name: "gyro",
        sensor: "gyro",
        units: "gyrons",
        range: {
          top: 0x0FFF,
          bottom: -255,
        },
        value: [1]
      };

      stub(utils, "twosToInt");
      utils.twosToInt.returns(255);
      spy(packet, "_parseBitmaskField");
    });

    afterEach(function() {
      utils.twosToInt.restore();
      packet._parseBitmaskField.restore();
    });

    it(" if val > field.range.top calls utils#twosToInt", function() {
      packet._parseBitmaskField(0xFF00, field, {});
      expect(utils.twosToInt).to.be.calledOnce;
      expect(utils.twosToInt).to.be.calledWith(0xFF00, 2);
    });

    it("adds to the array if field already exist", function() {
      packet._parseBitmaskField(0xFE, field, { gyro: field });
      field.value.push(255);
      expect(packet._parseBitmaskField)
        .to.have.returned(field);
    });
  });

});