diff --git a/.travis.yml b/.travis.yml index 038890a..1fdaa22 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,11 @@ deploy: secure: VTrqcB3d0y7jQWb3JuLO2TZTczArGnkOzJsik0vBVv17VB/nZyNkPlGfv1zdIIe5x3hMRwP2V/ual+MMEldLhMnGPI8359XYx6D/NAv+P8MmjwbsVbwo8hRG0ctXlDzHI9ni07zh6UaGsh/XiMEtm1sp1ZSzv8LMjNRetOEvP67SicqGvxD2mZHur30s8Fn0qTleamJddYVluJpvsCLYetobeGEghq+aS3l5A1zbnNDHsV54doyfBRjxMO05Awc8md84i4pZTbAGYFpGRxxkmCW6cyUzgWQnIlKUWSypDslwNRJOCzVIZwaXj8uyrnXCk/Bp0eZiooU5CiMjbLMoF2QMxd94vDyIxju/pYnq+yhMsTTdEHm2ZPZCJxdyMbqc0HscvouPi2va6MYXq3XRtR80kDvlAIWYWMJ8pPuSnlSRFUrk5AtQ6MygJeJxyANc+b45uyHQ1PtfBZd3TLm+PNhEZVFEoe2CAvK6IVaNoac7rMLurM/g/vtjrwbCDFBXvMMEUKuhpiVTyuig0V8lRN8IyEhytFnbMmsdFj0D+/B479fpZYOUAq2lXoJ9CxVn/qaZDYTx8DD7e/mP1xm9xJRcwCPB+RuQqDPkqGdRjXcVhkxzVSCQUHR9fOg2LyI1WHOVoc1gfaYt2kSJZ5seKmvATrp5u++yKPIdvkAR1AU= on: tags: true - repo: barracksiot/javascript-client \ No newline at end of file + repo: barracksiot/javascript-client + +notifications: + slack: + rooms: + secure: "ctLNliKum/SL5FhQ5PvrNkf67/fq6f6ud0V7XyZLBw5JHhn+ro8FoTY+IhZz3qh6F0AqcO0xItzBFEsY66D6gd3x+1QzYgdPvBkB42MDmE6abFrLy8LBrRpin1UEm1rtw9gQenwm+xMi8eGzytX8cSoZWR689h5mcYoN6tpWh8rxYny2XuY8BOHbLuTOJbbHpvDkAJqaZTgfhxlwhfIMKrCVvmenrvBRXuNP/mq/xxYo2Go2+H72d2IKOd1gOv7GsALhLOd3UAbhXeoK3pOUxw4jQCMYR7PjlT+LXjt4MVqElce998tbf+FAPTw9QVJc6y9jLXcto5jRBpeEdx/Wey457lBemaga375rzz3EM0gvSjwB+EhHZlq9nlhXB4iUVrZ3TiMRX6ZG6o3JoZXh7CTq2z8Mc9e0GqQ7/V21jHIuMxeUtCCTAjlRH+/5tlkwPGpEQ73UYDGdK2qjc9AWgkZZ08/ur5oEY4Mw6p5B2GcxPobgfVH/bp8ldhBrBJM0A4er7hIXimckRORvFZNg+jRTCNQYPLpyRSaIiJWGfGXaiitcaYO8z9lv835QpDbSxM3rEbGZOh+cDOHaoK7TUnw9kvai3mkgT6xkyKfj4ewC/3r0TpMvHlYpI3eRr0vkVkTLLGZedB7jOuYve31cHFDrlNSEDSw+Xwkcj1ukS8w=" + on_success: change + on_failure: always \ No newline at end of file diff --git a/README.md b/README.md index 24fb1f9..69fcdee 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ var customClientData = { } }; -barracks.checkUpdate(packages, customClientData).then(function (packagesInfo) { +barracks.getDevicePackages(packages, customClientData).then(function (packagesInfo) { packagesInfo.available.forEach(function (packageInfo) { // Do something with the newly available packages }); @@ -84,7 +84,7 @@ barracks.checkUpdate(packages, customClientData).then(function (packagesInfo) { }); ``` -The ```checkUpdate``` response is always as follow : +The ```getDevicePackages``` response is always as follow : ```js { @@ -94,8 +94,9 @@ The ```checkUpdate``` response is always as follow : reference: "abc.edf", version: "0.0.1", url: "https://app.barracks.io/path/to/package/version/", - size: 42, md5: "deadbeefbadc0ffee", + size: 42, + filename: 'aFile.sh', download: function (filePath) {} // Function to download package } ], @@ -105,8 +106,9 @@ The ```checkUpdate``` response is always as follow : reference: "abc.edf", version: "0.0.1", url: "https://app.barracks.io/path/to/package/version/", - size: 42, md5: "deadbeefbadc0ffee", + size: 42, + filename: 'aFile.sh', download: function (filePath) {} // Function to download package } ], @@ -128,7 +130,10 @@ The ```checkUpdate``` response is always as follow : ### Download a package -Once you have the response from checkUpdate, you'll be able to download file for all packages that are available for the device (packages that are in the ```available```, and ```changed``` lists of the response). +Once you have the response from getDevicePackages, you'll be able to download file for all packages that are available for the device (packages that are in the ```available```, and ```changed``` lists of the response). + +The ```filePath``` argument of the download function is optionnal. The default value will be as follow: +```_``` ```js var packages = [ @@ -142,7 +147,7 @@ var packages = [ } ]; -barracks.checkUpdate(packages, customClientData).then(function (packagesInfo) { +barracks.getDevicePackages(packages, customClientData).then(function (packagesInfo) { var downloadAvailablePackagesPromise = Promise.all( packagesInfo.available.map(function (packageInfo) { return packageInfo.download('/tmp/' + package.filename); // Return a Promise @@ -180,12 +185,13 @@ All errors returned by the SDK follow the same object format: Error type can be one of the the following: -* `REQUEST_FAILED`, is returned by both `Barracks.checkUpdate()` and `Barracks.checkUpdateAndDownload()` methods if the check update request fails. The error object also contains one additional property `requestError` that is the `Error` object returned by the [request](https://www.npmjs.com/package/request) library. -* `UNEXPECTED_SERVER_RESPONSE`, is returned by both `Barracks.checkUpdate()` and `Barracks.checkUpdateAndDownload()` methods if the HTTP response code is not `200` (a new update is available) or `204` (no update available). -* `DOWNLOAD_FAILED`, is returned by both `Update.download()` and `Barracks.checkUpdateAndDownload()` methods if the download of an update package fails. -* `DELETE_FILE_FAILED`, is returned by both `Update.download()` and `Barracks.checkUpdateAndDownload()` methods if the SDK fail to delete an update package that did not pass the MD5 checksum verification. -* `CHECKSUM_VERIFICATION_FAILED`, is returned by both `Update.download()` and `Barracks.checkUpdateAndDownload()` methods if the MD5 checksum verification of the update package downloaded fails. -* `MD5_HASH_CREATION_FAILED`, is returned by both `Update.download()` and `Barracks.checkUpdateAndDownload()` methods if the SDK is not able to generate the MD5 checksum of the update package downloaded. +* `MISSING_MANDATORY_ARGUMENT`, is returned by both `Barracks.getDevicePackages()` and `Package.download()`. It indicate that one or more of the mandatory arguments are missing. +* `REQUEST_FAILED`, is returned by `Barracks.getDevicePackages()` method if the getDevicePackage request fails. The error object also contains one additional property `requestError` that is the `Error` object returned by the [request](https://www.npmjs.com/package/request) library. +* `UNEXPECTED_SERVER_RESPONSE`, is returned by `Barracks.getDevicePackages()` method if the HTTP response code is not `200`. +* `DOWNLOAD_FAILED`, is returned by `Package.download()` method if the download of a package fails. +* `DELETE_FILE_FAILED`, is returned by `Package.download()` method if the SDK fail to delete a package that did not pass the MD5 checksum verification. +* `CHECKSUM_VERIFICATION_FAILED`, is returned by `Package.download()` method if the MD5 checksum verification of the package downloaded fails. +* `MD5_HASH_CREATION_FAILED`, is returned by `Package.download()` method if the SDK is not able to generate the MD5 checksum of the package downloaded. ## Docs & Community diff --git a/example/index.js b/example/index.js index f853c7f..4a9f413 100644 --- a/example/index.js +++ b/example/index.js @@ -24,6 +24,12 @@ if (!barracksApiKey) { } var unitId = 'SDK-example-unit'; +var customClientData = { + type: 'alpha', + extra: { + app2: true + } +}; var packages = [ { @@ -43,7 +49,6 @@ var packages = [ var barracks = new Barracks({ baseURL: barracksBaseUrl, apiKey: barracksApiKey, - unitId: unitId, allowSelfSigned: (isSelfSigned ? (isSelfSigned === '1') : false) }); @@ -91,7 +96,7 @@ function handleUnavailablePackages(packages) { function waitAndDisplayUpdate() { setTimeout(function () { - barracks.checkUpdate(packages, { test: 'coucou' }).then(function (response) { + barracks.getDevicePackages(unitId, packages, customClientData).then(function (response) { handleAvailablePackages(response.available); handleChangedPackages(response.changed); handleUnchangedPackages(response.unchanged); diff --git a/package.json b/package.json index a718087..54bad49 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,8 @@ "dependencies": { "es6-promise": "4.0.5", "md5-file": "3.1.1", - "request": "2.74.0" + "request": "2.74.0", + "uuid": "^3.0.1" }, "engines": { "node": ">=0.10 <7.0" diff --git a/src/index.js b/src/index.js index 99bb2df..d251480 100644 --- a/src/index.js +++ b/src/index.js @@ -1,23 +1,24 @@ 'use strict'; -var ERROR_REQUEST_FAILED = 'REQUEST_FAILED'; -var ERROR_DOWNLOAD_FAILED = 'DOWNLOAD_FAILED'; -var ERROR_UNEXPECTED_SERVER_RESPONSE = 'UNEXPECTED_SERVER_RESPONSE'; +var ERROR_REQUEST_FAILED = 'REQUEST_FAILED'; +var ERROR_DOWNLOAD_FAILED = 'DOWNLOAD_FAILED'; +var ERROR_UNEXPECTED_SERVER_RESPONSE = 'UNEXPECTED_SERVER_RESPONSE'; +var ERROR_MISSING_MANDATORY_ARGUMENT = 'MISSING_MANDATORY_ARGUMENT'; -var DEFAULT_BARRACKS_BASE_URL = 'https://app.barracks.io'; -var CHECK_UPDATE_ENDPOINT = '/api/device/resolve'; +var DEFAULT_BARRACKS_BASE_URL = 'https://app.barracks.io'; +var GET_DEVICE_PACKAGES_ENDPOINT = '/api/device/resolve'; require('es6-promise').polyfill(); var responseBuilder = require('./responseBuilder'); var fs = require('fs'); var request = require('request'); var fileHelper = require('./fileHelper'); +var uuid = require('uuid/v1'); function Barracks(options) { this.options = { baseURL: options.baseURL || DEFAULT_BARRACKS_BASE_URL, - apiKey: options.apiKey, - unitId: options.unitId + apiKey: options.apiKey }; if (options.allowSelfSigned && options.allowSelfSigned === true) { @@ -25,24 +26,31 @@ function Barracks(options) { } } -Barracks.prototype.checkUpdate = function (packages, customClientData) { +Barracks.prototype.getDevicePackages = function(unitId, packages, customClientData) { var that = this; - return new Promise(function (resolve, reject) { + return new Promise(function(resolve, reject) { + if (!unitId || !packages) { + reject({ + type: ERROR_MISSING_MANDATORY_ARGUMENT, + message: 'missing or empty unitId or packages arguments' + }); + } + var requestOptions = { - url: that.options.baseURL + CHECK_UPDATE_ENDPOINT, + url: that.options.baseURL + GET_DEVICE_PACKAGES_ENDPOINT, method: 'POST', headers: { 'Authorization': that.options.apiKey, 'Content-type': 'application/json' }, body: JSON.stringify({ - unitId: that.options.unitId, + unitId: unitId, customClientData: customClientData, - components: packages + packages: packages }) }; - request(requestOptions, function (error, response, body) { + request(requestOptions, function(error, response, body) { if (error) { reject({ type: ERROR_REQUEST_FAILED, @@ -61,9 +69,18 @@ Barracks.prototype.checkUpdate = function (packages, customClientData) { }); }; -Barracks.prototype.downloadPackage = function (packageInfo, filePath) { +Barracks.prototype.downloadPackage = function(packageInfo, filePath) { var that = this; - return new Promise(function (resolve, reject) { + return new Promise(function(resolve, reject) { + if (!packageInfo) { + reject({ + type: ERROR_MISSING_MANDATORY_ARGUMENT, + message: 'missing or empty packageInfo argument' + }); + } + + filePath = filePath || uuid() + '_' + packageInfo.filename; + var downloadParams = { url: packageInfo.url, method: 'GET', @@ -73,25 +90,25 @@ Barracks.prototype.downloadPackage = function (packageInfo, filePath) { }; var fileStream = fs.createWriteStream(filePath); - request(downloadParams).on('response', function (response) { + request(downloadParams).on('response', function(response) { if (response.statusCode != 200) { fileStream.emit('error', { type: ERROR_DOWNLOAD_FAILED, message: 'Server replied with HTTP ' + response.statusCode }); } - }).pipe(fileStream).on('close', function () { - fileHelper.checkMd5(filePath, packageInfo.md5).then(function () { + }).pipe(fileStream).on('close', function() { + fileHelper.checkMd5(filePath, packageInfo.md5).then(function() { resolve(filePath); - }).catch(function (err) { + }).catch(function(err) { fileHelper.deleteFile(filePath, reject); reject(err); }); - }).on('error', function (err) { + }).on('error', function(err) { fileHelper.deleteFile(filePath, reject); reject(err); }); }); }; -module.exports = Barracks; \ No newline at end of file +module.exports = Barracks; diff --git a/tests/index_test.js b/tests/index_test.js index e8bc554..8b135c6 100644 --- a/tests/index_test.js +++ b/tests/index_test.js @@ -5,47 +5,49 @@ var sinon = require('sinon'); var sinonChai = require('sinon-chai'); var chai = require('chai'); var expect = chai.expect; -var proxyquire = require('proxyquire'); +var proxyquire = require('proxyquire'); var Stream = require('stream'); chai.use(sinonChai); var UNIT_ID = 'unit1'; var API_KEY = 'validKey'; +var CUSTOM_CLIENT_DATA = { + aKey: 'aValue', + anotherKey: true +}; -var component1 = { - reference: 'component.1.ref', +var package1 = { + reference: 'package.1.ref', version: '1.2.3' }; -var component2 = { - reference: 'component.2.ref', +var package2 = { + reference: 'package.2.ref', version: '4.5.6' }; -describe('Constructor : ', function () { +describe('Constructor : ', function() { var Barracks; - beforeEach(function () { + beforeEach(function() { Barracks = require('../src/index.js'); }); function validateBarracksObject(barracks, expectedBaseUrl) { expect(barracks).to.be.an('object'); expect(barracks.options).to.be.an('object'); - expect(barracks.checkUpdate).to.be.a('function'); + expect(barracks.getDevicePackages).to.be.a('function'); expect(barracks.options).to.deep.equals({ apiKey: API_KEY, - unitId: UNIT_ID, baseURL: expectedBaseUrl }); } - it('Should return the Barracks object with default values when minimums options given', function () { + it('Should return the Barracks object with default values when minimums options given', function() { // Given var options = { - apiKey: API_KEY, - unitId: UNIT_ID + apiKey: API_KEY }; // When @@ -55,12 +57,11 @@ describe('Constructor : ', function () { validateBarracksObject(barracks, 'https://app.barracks.io'); }); - it('Should return the Barracks object with baseUrl overriden when url option given', function () { + it('Should return the Barracks object with baseUrl overriden when url option given', function() { // Given var url = 'not.barracks.io'; var options = { apiKey: API_KEY, - unitId: UNIT_ID, baseURL: url }; @@ -71,11 +72,10 @@ describe('Constructor : ', function () { validateBarracksObject(barracks, url); }); - it('Should return the Barracks object that do not accept self signed cert when option given with invalid value', function () { + it('Should return the Barracks object that do not accept self signed cert when option given with invalid value', function() { // Given var options = { apiKey: API_KEY, - unitId: UNIT_ID, allowSelfSigned: 'plop' }; @@ -87,11 +87,10 @@ describe('Constructor : ', function () { expect(process.env.NODE_TLS_REJECT_UNAUTHORIZED).to.be.equals(undefined); }); - it('Should return the Barracks object thta accept self signed cert when option given', function () { + it('Should return the Barracks object thta accept self signed cert when option given', function() { // Given var options = { apiKey: API_KEY, - unitId: UNIT_ID, allowSelfSigned: true }; @@ -104,16 +103,16 @@ describe('Constructor : ', function () { }); }); -describe('checkUpdate(components, customClientData) ', function () { +describe('getDevicePackages(unitId, packages, customClientData) ', function() { var barracks; - var checkUpdateComponentsUrl = '/api/device/resolve'; - var requestMock = function () {}; - var buildResponseMock = function () {}; + var getDevicePackagesUrl = '/api/device/resolve'; + var requestMock = function() {}; + var buildResponseMock = function() {}; - function getRequestPayloadForComponents(components) { + function getRequestPayloadForPackages(packages, customClientData) { return { - url: 'https://app.barracks.io' + checkUpdateComponentsUrl, + url: 'https://app.barracks.io' + getDevicePackagesUrl, method: 'POST', headers: { 'Authorization': API_KEY, @@ -121,22 +120,22 @@ describe('checkUpdate(components, customClientData) ', function () { }, body: JSON.stringify({ unitId: UNIT_ID, - customClientData: undefined, - components: components + customClientData: customClientData, + packages: packages }) }; } - beforeEach(function () { - requestMock = function () {}; - buildResponseMock = function () {}; + beforeEach(function() { + requestMock = function() {}; + buildResponseMock = function() {}; var Barracks = proxyquire('../src/index.js', { - 'request': function (options, callback) { + 'request': function(options, callback) { return requestMock(options, callback); }, './responseBuilder': { - buildResponse: function (body, downloadFunction) { + buildResponse: function(body, downloadFunction) { return buildResponseMock(body, downloadFunction); } } @@ -148,20 +147,80 @@ describe('checkUpdate(components, customClientData) ', function () { }); }); - it('Should return request failed error when request failed', function (done) { + it('Should reject MISSING_MANDATORY_ARGUMENT error when no unitId given', function(done) { + // Given + var packages = [package1, package2]; + + // When / Then + barracks.getDevicePackages(undefined, packages).then(function() { + done('should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty unitId or packages arguments' + }); + done(); + }); + }); + + it('Should reject MISSING_MANDATORY_ARGUMENT error when empty unitId given', function(done) { + // Given + var packages = [package1, package2]; + + // When / Then + barracks.getDevicePackages('', packages).then(function() { + done('should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty unitId or packages arguments' + }); + done(); + }); + }); + + it('Should reject MISSING_MANDATORY_ARGUMENT error when no packages given', function(done) { + // When / Then + barracks.getDevicePackages(UNIT_ID).then(function() { + done('should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty unitId or packages arguments' + }); + done(); + }); + }); + + it('Should reject MISSING_MANDATORY_ARGUMENT error when empty packages given', function(done) { + // When / Then + barracks.getDevicePackages(UNIT_ID, '').then(function() { + done('should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty unitId or packages arguments' + }); + done(); + }); + }); + + it('Should return request failed error when request failed', function(done) { // Given - var error = { message: 'Error occured' }; - var components = [ component1, component2 ]; + var error = { + message: 'Error occured' + }; + var packages = [package1, package2]; var requestSpy = sinon.spy(); - requestMock = function (options, callback) { + requestMock = function(options, callback) { requestSpy(options, callback); callback(error); }; // When / Then - barracks.checkUpdate(components).then(function () { + barracks.getDevicePackages(UNIT_ID, packages).then(function() { done('should have failed'); - }).catch(function (err) { + }).catch(function(err) { expect(err).to.deep.equals({ type: 'REQUEST_FAILED', requestError: error, @@ -169,152 +228,202 @@ describe('checkUpdate(components, customClientData) ', function () { }); expect(requestSpy).to.have.been.calledOnce; expect(requestSpy).to.have.been.calledWithExactly( - getRequestPayloadForComponents(components), + getRequestPayloadForPackages(packages), sinon.match.func ); done(); }); }); - it('Should return unexpected server response error when server do not return 200 OK', function (done) { + it('Should return unexpected server response error when server do not return 200 OK', function(done) { // Given - var response = { body: 'Internal error', statusCode: 500 }; - var components = [ component1, component2 ]; + var response = { + body: 'Internal error', + statusCode: 500 + }; + var packages = [package1, package2]; var requestSpy = sinon.spy(); - requestMock = function (options, callback) { + requestMock = function(options, callback) { requestSpy(options, callback); callback(undefined, response, response.body); }; // When / Then - barracks.checkUpdate(components).then(function () { + barracks.getDevicePackages(UNIT_ID, packages).then(function() { done('should have failed'); - }).catch(function (err) { + }).catch(function(err) { expect(err).to.deep.equals({ type: 'UNEXPECTED_SERVER_RESPONSE', message: response.body }); expect(requestSpy).to.have.been.calledOnce; expect(requestSpy).to.have.been.calledWithExactly( - getRequestPayloadForComponents(components), + getRequestPayloadForPackages(packages), + sinon.match.func + ); + done(); + }); + }); + + it('Should return server response when server return 200 OK', function(done) { + // Given + var packageInfo = { + available: [], + changed: [], + unchanged: [], + unavailable: [] + }; + var response = { + body: JSON.stringify(packageInfo), + statusCode: 200 + }; + var packages = [package1, package2]; + var requestSpy = sinon.spy(); + requestMock = function(options, callback) { + requestSpy(options, callback); + callback(undefined, response, response.body); + }; + var buildResponseSpy = sinon.spy(); + buildResponseMock = function(body, downloadFunction) { + buildResponseSpy(body, downloadFunction); + return packageInfo; + }; + + // When / Then + barracks.getDevicePackages(UNIT_ID, packages).then(function(result) { + expect(result).to.deep.equals(packageInfo); + expect(requestSpy).to.have.been.calledOnce; + expect(requestSpy).to.have.been.calledWithExactly( + getRequestPayloadForPackages(packages), + sinon.match.func + ); + expect(buildResponseSpy).to.have.been.calledOnce; + expect(buildResponseSpy).to.have.been.calledWithExactly( + packageInfo, sinon.match.func ); done(); + }).catch(function(err) { + done(err); }); }); - it('Should return server response when server return 200 OK', function (done) { + it('Should send customClientData and return server response when server return 200 OK', function(done) { // Given - var componentInfo = { - available:[], - changed:[], - unchanged:[], - unavailable:[] + var packageInfo = { + available: [], + changed: [], + unchanged: [], + unavailable: [] }; var response = { - body: JSON.stringify(componentInfo), + body: JSON.stringify(packageInfo), statusCode: 200 }; - var components = [ component1, component2 ]; + var packages = [package1, package2]; var requestSpy = sinon.spy(); - requestMock = function (options, callback) { + requestMock = function(options, callback) { requestSpy(options, callback); callback(undefined, response, response.body); }; var buildResponseSpy = sinon.spy(); - buildResponseMock = function (body, downloadFunction) { + buildResponseMock = function(body, downloadFunction) { buildResponseSpy(body, downloadFunction); - return componentInfo; + return packageInfo; }; // When / Then - barracks.checkUpdate(components).then(function (result) { - expect(result).to.deep.equals(componentInfo); + barracks.getDevicePackages(UNIT_ID, packages, CUSTOM_CLIENT_DATA).then(function(result) { + expect(result).to.deep.equals(packageInfo); expect(requestSpy).to.have.been.calledOnce; expect(requestSpy).to.have.been.calledWithExactly( - getRequestPayloadForComponents(components), + getRequestPayloadForPackages(packages, CUSTOM_CLIENT_DATA), sinon.match.func ); expect(buildResponseSpy).to.have.been.calledOnce; expect(buildResponseSpy).to.have.been.calledWithExactly( - componentInfo, + packageInfo, sinon.match.func ); done(); - }).catch(function (err) { + }).catch(function(err) { done(err); }); }); - it('Should return server response when server return 200 OK', function (done) { + it('Should return server response when server return 200 OK', function(done) { // Given - var componentInfo = { - available:[], - changed:[], - unchanged:[], - unavailable:[] + var packageInfo = { + available: [], + changed: [], + unchanged: [], + unavailable: [] }; var response = { - body: JSON.stringify(componentInfo), + body: JSON.stringify(packageInfo), statusCode: 200 }; - var components = [ component1, component2 ]; + var packages = [package1, package2]; var requestSpy = sinon.spy(); - requestMock = function (options, callback) { + requestMock = function(options, callback) { requestSpy(options, callback); callback(undefined, response, response.body); }; var buildResponseSpy = sinon.spy(); - buildResponseMock = function (body, downloadFunction) { + buildResponseMock = function(body, downloadFunction) { buildResponseSpy(body, downloadFunction); - return componentInfo; + return packageInfo; }; // When / Then - barracks.checkUpdate(components).then(function (result) { - expect(result).to.deep.equals(componentInfo); + barracks.getDevicePackages(UNIT_ID, packages).then(function(result) { + expect(result).to.deep.equals(packageInfo); expect(requestSpy).to.have.been.calledOnce; expect(requestSpy).to.have.been.calledWithExactly( - getRequestPayloadForComponents(components), + getRequestPayloadForPackages(packages), sinon.match.func ); expect(buildResponseSpy).to.have.been.calledOnce; expect(buildResponseSpy).to.have.been.calledWithExactly( - componentInfo, + packageInfo, sinon.match.func ); done(); - }).catch(function (err) { + }).catch(function(err) { done(err); }); }); }); -describe('downloadPackage(packageInfo, filePath) ', function () { +describe('downloadPackage(packageInfo, filePath) ', function() { var barracks; - var createWriteStreamMock = function () {}; - var checkMd5Mock = function () {}; - var deleteFileMock = function () {}; - var requestMock = function () {}; + var createWriteStreamMock = function() {}; + var checkMd5Mock = function() {}; + var deleteFileMock = function() {}; + var requestMock = function() {}; + var uuidMock = function() {}; - beforeEach(function () { + beforeEach(function() { var Barracks = proxyquire('../src/index.js', { 'fs': { - createWriteStream: function (path) { + createWriteStream: function(path) { return createWriteStreamMock(path); } }, './fileHelper': { - checkMd5: function (file, checksum) { + checkMd5: function(file, checksum) { return checkMd5Mock(file, checksum); }, - deleteFile: function (file, reject) { + deleteFile: function(file, reject) { return deleteFileMock(file, reject); } }, - 'request': function (params) { + 'request': function(params) { return requestMock(params); + }, + 'uuid/v1': function() { + return uuidMock(); } }); @@ -324,9 +433,37 @@ describe('downloadPackage(packageInfo, filePath) ', function () { }); }); - it('Should return ERROR_DOWNLOAD_FAILED when server return http code other than 200 OK', function (done) { + it('Should reject MISSING_MANDATORY_ARGUMENT error when no packageInfo given', function(done) { + // When / Then + barracks.downloadPackage().then(function() { + done('Should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty packageInfo argument' + }); + done(); + }); + }); + + it('Should reject MISSING_MANDATORY_ARGUMENT error when empty packageInfo given', function(done) { + // When / Then + barracks.downloadPackage('').then(function() { + done('Should have failed'); + }).catch(function(err) { + expect(err).to.deep.equals({ + type: 'MISSING_MANDATORY_ARGUMENT', + message: 'missing or empty packageInfo argument' + }); + done(); + }); + }); + + it('Should return ERROR_DOWNLOAD_FAILED when server return http code other than 200 OK', function(done) { // Given - var response = { statusCode: 500 }; + var response = { + statusCode: 500 + }; var packageInfo = { package: 'abc.edf', version: '0.0.1', @@ -338,31 +475,31 @@ describe('downloadPackage(packageInfo, filePath) ', function () { var fileStream = new Stream(); var createWriteStreamSpy = sinon.spy(); - createWriteStreamMock = function (path) { + createWriteStreamMock = function(path) { createWriteStreamSpy(path); return fileStream; }; var requestStream = new Stream(); var requestSpy = sinon.spy(); - requestMock = function (params) { + requestMock = function(params) { requestSpy(params); return requestStream; }; var deleteFileSpy = sinon.spy(); - deleteFileMock = function (file, reject) { + deleteFileMock = function(file, reject) { deleteFileSpy(file, reject); }; - setTimeout(function () { + setTimeout(function() { requestStream.emit('response', response); }, 75); // When / Then - barracks.downloadPackage(packageInfo, filePath).then(function () { + barracks.downloadPackage(packageInfo, filePath).then(function() { done('Should have failed'); - }).catch(function (err) { + }).catch(function(err) { expect(err).to.deep.equals({ type: 'DOWNLOAD_FAILED', message: 'Server replied with HTTP ' + response.statusCode @@ -386,9 +523,11 @@ describe('downloadPackage(packageInfo, filePath) ', function () { }); }); - it('Should return an error when md5 check fail', function (done) { + it('Should return an error when md5 check fail', function(done) { // Given - var response = { statusCode: 200 }; + var response = { + statusCode: 200 + }; var md5Error = 'MD5 do not match !!'; var packageInfo = { package: 'abc.edf', @@ -401,40 +540,40 @@ describe('downloadPackage(packageInfo, filePath) ', function () { var fileStream = new Stream(); var createWriteStreamSpy = sinon.spy(); - createWriteStreamMock = function (path) { + createWriteStreamMock = function(path) { createWriteStreamSpy(path); return fileStream; }; var requestStream = new Stream(); var requestSpy = sinon.spy(); - requestMock = function (params) { + requestMock = function(params) { requestSpy(params); return requestStream; }; var checkMd5Spy = sinon.spy(); - checkMd5Mock = function (path, checksum) { + checkMd5Mock = function(path, checksum) { checkMd5Spy(path, checksum); return Promise.reject(md5Error); }; var deleteFileSpy = sinon.spy(); - deleteFileMock = function (file, reject) { + deleteFileMock = function(file, reject) { deleteFileSpy(file, reject); }; - setTimeout(function () { + setTimeout(function() { requestStream.emit('response', response); }, 75); - setTimeout(function () { + setTimeout(function() { fileStream.emit('close'); }, 95); // When / Then - barracks.downloadPackage(packageInfo, filePath).then(function () { + barracks.downloadPackage(packageInfo, filePath).then(function() { done('Should have failed'); - }).catch(function (err) { + }).catch(function(err) { expect(err).to.be.equals(md5Error); expect(createWriteStreamSpy).to.have.been.calledOnce; expect(createWriteStreamSpy).to.have.been.calledWithExactly(filePath); @@ -460,9 +599,11 @@ describe('downloadPackage(packageInfo, filePath) ', function () { }); }); - it('Should return the path to downloaded file when request successful', function (done) { + it('Should return the path to downloaded file when request successful', function(done) { // Given - var response = { statusCode: 200 }; + var response = { + statusCode: 200 + }; var packageInfo = { package: 'abc.edf', version: '0.0.1', @@ -474,33 +615,33 @@ describe('downloadPackage(packageInfo, filePath) ', function () { var fileStream = new Stream(); var createWriteStreamSpy = sinon.spy(); - createWriteStreamMock = function (path) { + createWriteStreamMock = function(path) { createWriteStreamSpy(path); return fileStream; }; var requestStream = new Stream(); var requestSpy = sinon.spy(); - requestMock = function (params) { + requestMock = function(params) { requestSpy(params); return requestStream; }; var checkMd5Spy = sinon.spy(); - checkMd5Mock = function (path, checksum) { + checkMd5Mock = function(path, checksum) { checkMd5Spy(path, checksum); return Promise.resolve(); }; - setTimeout(function () { + setTimeout(function() { requestStream.emit('response', response); }, 75); - setTimeout(function () { + setTimeout(function() { fileStream.emit('close'); }, 95); // When / Then - barracks.downloadPackage(packageInfo, filePath).then(function (result) { + barracks.downloadPackage(packageInfo, filePath).then(function(result) { expect(result).to.be.equals(filePath); expect(createWriteStreamSpy).to.have.been.calledOnce; expect(createWriteStreamSpy).to.have.been.calledWithExactly(filePath); @@ -518,7 +659,83 @@ describe('downloadPackage(packageInfo, filePath) ', function () { packageInfo.md5 ); done(); - }).catch(function (err) { + }).catch(function(err) { + done(err); + }); + }); + + it('Should generate random path for the file to download when no filePath given', function(done) { + // Given + var response = { + statusCode: 200 + }; + var packageInfo = { + package: 'abc.edf', + version: '0.0.1', + url: 'https://not.barracks.io/path/to/file', + filename: 'myFile.sh', + size: 42, + md5: 'deadbeefbadc0ffee' + }; + + var fileStream = new Stream(); + var createWriteStreamSpy = sinon.spy(); + createWriteStreamMock = function(path) { + createWriteStreamSpy(path); + return fileStream; + }; + + var requestStream = new Stream(); + var requestSpy = sinon.spy(); + requestMock = function(params) { + requestSpy(params); + return requestStream; + }; + + var checkMd5Spy = sinon.spy(); + checkMd5Mock = function(path, checksum) { + checkMd5Spy(path, checksum); + return Promise.resolve(); + }; + + var uuidSpy = sinon.spy(); + var randomUuid = 'qaserdxcftygvghujhnbnjkmklknbhgfcxdesazw'; + uuidMock = function() { + uuidSpy(); + return randomUuid; + }; + + var expectedFilePath = randomUuid + '_' + packageInfo.filename; + + setTimeout(function() { + requestStream.emit('response', response); + }, 75); + setTimeout(function() { + fileStream.emit('close'); + }, 95); + + // When / Then + barracks.downloadPackage(packageInfo).then(function(result) { + expect(result).to.be.equals(expectedFilePath); + expect(uuidSpy).to.have.been.calledOnce; + expect(uuidSpy).to.have.been.calledWithExactly(); + expect(createWriteStreamSpy).to.have.been.calledOnce; + expect(createWriteStreamSpy).to.have.been.calledWithExactly(expectedFilePath); + expect(requestSpy).to.have.been.calledOnce; + expect(requestSpy).to.have.been.calledWithExactly({ + url: packageInfo.url, + method: 'GET', + headers: { + Authorization: API_KEY + } + }); + expect(checkMd5Spy).to.have.been.calledOnce; + expect(checkMd5Spy).to.have.been.calledWithExactly( + expectedFilePath, + packageInfo.md5 + ); + done(); + }).catch(function(err) { done(err); }); });