diff --git a/.eslintrc b/.eslintrc index ec44b7e..723509a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,8 +1,17 @@ { "env": { - "node": true, - "mocha": true, - "es6": true + "node": true, + "mocha": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 8, + "ecmaFeatures": { + "impliedStrict": true + } + }, + "globals": { + }, "rules": { // Strict mode to support babel @@ -12,7 +21,6 @@ "no-console": 1, // disallow use of console (off by default in the node environment) "no-constant-condition": 2, // disallow use of constant expressions in conditions "no-control-regex": 2, // disallow control characters in regular expressions - "no-debugger": 2, // disallow use of debugger "no-dupe-args": 2, // disallow duplicate arguments in functions "no-dupe-keys": 2, // disallow duplicate keys when creating object literals "no-duplicate-case": 2, // disallow a duplicate case label. @@ -122,5 +130,6 @@ "comma-dangle": 1, "key-spacing": [1, { "align": "value" }], "no-param-reassign": 0, + "no-debugger": 1, // disallow use of debugger } } diff --git a/.vscode/settings.json b/.vscode/settings.json index fe71598..932dfaa 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "python.linting.pylintEnabled": false + "python.linting.pylintEnabled": false, + "mocha.enabled": true } \ No newline at end of file diff --git a/__tests__/ha-api.spec.js b/__tests__/ha-api.spec.js new file mode 100644 index 0000000..e592317 --- /dev/null +++ b/__tests__/ha-api.spec.js @@ -0,0 +1,36 @@ +const should = require('should'); +const nock = require('nock'); +const smock = require('simple-mock'); +const HaApi = require('../lib/ha-api'); + +const TEST_CONFIG = { + baseUrl: 'http://bogus', + apiPass: 'bogus' +}; + +describe('HaApi Tests', function() { + afterEach(function () { + smock.restore(); + }) + + describe('instantiation', function() { + it('should instantiate correctly', function () { + const haApi = new HaApi(TEST_CONFIG); + + haApi.config.should.equal(TEST_CONFIG); + haApi.client.defaults.headers['x-ha-access'].should.equal(TEST_CONFIG.apiPass); + haApi.client.defaults.baseURL.should.equal(`${TEST_CONFIG.baseUrl}/api`); + }); + }) + + describe('API Calls', function () { + it('should get services', async function() { + nock(TEST_CONFIG.baseUrl).get('/api/services').reply(200, 'testing'); + + const haApi = new HaApi(TEST_CONFIG); + const services = await haApi.getServices(); + services.should.equal('testing'); + }) + }) +}); + diff --git a/__tests__/ha-events.spec.js b/__tests__/ha-events.spec.js new file mode 100644 index 0000000..125714f --- /dev/null +++ b/__tests__/ha-events.spec.js @@ -0,0 +1,31 @@ +require('should'); +const smock = require('simple-mock'); +const HaEvents = require('../lib/ha-events'); + +const TEST_CONFIG = { + baseUrl: 'http://bogus', + apiPass: 'bogus', + events: { + transport: 'sse', // For future support of websockets + retries: { + maxAttempts: 10, // How many times to retry connection + delay: 5000 // Delay this long before retry (in ms) + } + } +}; + +describe('HaEvents Tests', function() { + afterEach(function () { + smock.restore(); + }) + + describe('instantiation', function() { + it('should instantiate correctly', function () { + const haEvents = new HaEvents(TEST_CONFIG); + + haEvents.config.should.equal(TEST_CONFIG); + haEvents.streamUrl.should.equal(`${TEST_CONFIG.baseUrl}/api/stream`); + }); + }) +}); + diff --git a/__tests__/node-home-assistant.spec.js b/__tests__/node-home-assistant.spec.js new file mode 100644 index 0000000..229eb64 --- /dev/null +++ b/__tests__/node-home-assistant.spec.js @@ -0,0 +1,36 @@ +const should = require('should'); +const nock = require('nock'); +const smock = require('simple-mock'); +const HomeAssistant = require('..'); + +describe('HomeAssistant Tests', function() { + afterEach(function () { + smock.restore(); + }) + + it('should allow instantiation without config', async function () { + const homeAssistant = await new HomeAssistant(); + should.exist(homeAssistant); + }); + + it('should automatically start listenting during instantiation if startListening === true', async function() { + const homeAssistantExpected = {}; + const startListeningProxy = smock.mock(HomeAssistant.prototype, 'startListening').resolveWith(homeAssistantExpected); + + const homeAssistant = await new HomeAssistant({ baseUrl: 'http://localhost:8123' }, { startListening: true }); + should.exist(homeAssistant); + startListeningProxy.callCount.should.equal(1); + }); + + it('should test incorrect connection', async function() { + const isValidConnection = await HomeAssistant.testConnection({ baseUrl: 'http://bogus' }); + isValidConnection.should.equal(false); + }); + + it('should test correct connection', async function() { + nock('http://fakeHomeAssistant').get('/api/config').reply(200); + + const isValidConnection = await HomeAssistant.testConnection({ baseUrl: 'http://fakeHomeAssistant' }); + isValidConnection.should.equal(true); + }); +}); diff --git a/index.js b/index.js index 3db8329..b9b239a 100644 --- a/index.js +++ b/index.js @@ -1,2 +1 @@ -'use strict'; -module.exports = require('./lib/_index'); +module.exports = require('./lib/node-home-assistant'); diff --git a/lib/_index.js b/lib/_index.js deleted file mode 100644 index 2cdf797..0000000 --- a/lib/_index.js +++ /dev/null @@ -1,88 +0,0 @@ -'use strict'; -const debug = require('debug')('home-assistant'); -const HaEvents = require('./ha-events'); -const HaApi = require('./ha-api'); - -const DEFAULTS = { - baseUrl: 'http://localhost:8123', - apiPass: null, - api: {}, - events: { - transport: 'sse', // For future support of websockets - retries: { - maxAttempts: 10, // How many times to retry connection - delay: 5000 // Delay this long before retry (in ms) - } - } -}; - -function HomeAssistant(config) { - if (! (this instanceof HomeAssistant)) { return new HomeAssistant(config); } - this.config = Object.assign({}, DEFAULTS, config); - - this.events = new HaEvents(this.config); - this.api = new HaApi(this.config); - this._init(); -} - -HomeAssistant.prototype._init = function () { - // Get the initial state list - // create state listener to watch events and keep local copy of updated states - this.api.getStates() - .then(states => (this.states = states)) - .then(() => this.events.on('ha_events:state_changed', (evt) => this._onStateChanged(evt))) - .catch(debug); - this.api.getServices() - .then(services => (this.availableServices = services)) - .catch(debug); - this.api.getEvents() - .then(events => (this.availableEvents = events)) - .catch(debug); -}; - -HomeAssistant.prototype.getStates = function () { - let currentStates = this.states - ? Promise.resolve(this.states) - : this.api.getStates(); - - return currentStates - .then(states => { - this.states = states; - return states; - }); -} - -HomeAssistant.prototype.getServices = function () { - let availableServices = this.availableServices - ? Promise.resolve(this.availableServices) - : this.api.getServices(); - - return availableServices - .then(services => { - this.availableServices = services; - return services; - }); -} - -HomeAssistant.prototype.getEvents = function () { - let availableEvents = this.availableEvents - ? Promise.resolve(this.availableServices) - : this.api.getEvents(); - - return availableEvents - .then(events => { - this.availableEvents = events; - return events; - }); -} - -HomeAssistant.prototype._onStateChanged = function (changedEntity) { - debug(`changedEntity: ${JSON.stringify(changedEntity)}`); - const cachedEntity = this.states[changedEntity.entity_id]; - - if (!cachedEntity) { - debug('Got state changed event for entity that was not know, this should not happen'); - } - this.states[changedEntity.entity_id] = changedEntity.event.new_state; -}; -module.exports = HomeAssistant; diff --git a/lib/ha-api.js b/lib/ha-api.js index 99b68a5..857db0d 100644 --- a/lib/ha-api.js +++ b/lib/ha-api.js @@ -1,11 +1,8 @@ -'use strict'; const axios = require('axios'); const debug = require('debug')('home-assistant:api'); function HaApi(config) { if (! (this instanceof HaApi)) { return new HaApi(config); } - debug('instantiating api interface'); - this.config = config; const apiOpts = { baseURL: config.baseUrl + '/api' }; apiOpts.headers = (config.apiPass) diff --git a/lib/ha-events.js b/lib/ha-events.js index a172e38..6c4548d 100644 --- a/lib/ha-events.js +++ b/lib/ha-events.js @@ -1,4 +1,3 @@ -'use strict'; const inherits = require('util').inherits; const EventEmitter = require('events').EventEmitter; const EventSource = require('eventsource'); @@ -6,7 +5,6 @@ const debug = require('debug')('home-assistant:events'); function HaEvents(config) { if (! (this instanceof HaEvents)) { return new HaEvents(config); } - debug('instantiating events interface'); this.config = config; this.streamUrl = `${config.baseUrl}/api/stream`; @@ -15,22 +13,21 @@ function HaEvents(config) { : {}; this.connected = false; - // TODO: Implement websocket listener - if (config.events.transport === 'sse') { - debug ('setting up eventsource transport'); - this.client = new EventSource(this.streamUrl, this.esOptions); - this.client.on('message', (evt) => this.onClientMessage(evt)); - - this.client.on('open', () => this.onClientOpen()); - this.client.on('close', () => this.onClientClose()); - this.client.on('error', (err) => this.onClientError(err)); - } - EventEmitter.call(this); this.setMaxListeners(0); } inherits(HaEvents, EventEmitter); +HaEvents.prototype.startListening = function () { + if (this.config.events.transport !== 'sse') { throw new Error('Unsupported transport type'); } + + this.client = new EventSource(this.streamUrl, this.esOptions); + this.client.on('message', (evt) => this.onClientMessage(evt)); + + this.client.on('open', () => this.onClientOpen()); + this.client.on('close', () => this.onClientClose()); + this.client.on('error', (err) => this.onClientError(err)); +} HaEvents.prototype.onClientMessage = function(msg) { // debug('sse message event: ' + require('util').inspect(msg)); @@ -70,13 +67,8 @@ HaEvents.prototype.onClientOpen = function () { this.emit('ha_events:open'); }; -HaEvents.prototype.onClientClose = function () { - this.closeClient(null, 'events connection closed, cleaning up connection'); -}; - -HaEvents.prototype.onClientError = function (err) { - this.closeClient(err, 'events connection error, cleaning up connection'); -}; +HaEvents.prototype.onClientClose = function () { this.closeClient(null, 'events connection closed, cleaning up connection'); }; +HaEvents.prototype.onClientError = function (err) { this.closeClient(err, 'events connection error, cleaning up connection'); }; HaEvents.prototype.closeClient = function (err, logMsg) { if (logMsg) { debug(logMsg); } @@ -91,19 +83,8 @@ HaEvents.prototype.closeClient = function (err, logMsg) { this.emit('ha_events:close'); } - setTimeout(() => { - this.client = new EventSource(this.streamUrl, this.esOptions); - this.client.on('message', (evt) => this.onClientMessage(evt)); - - this.client.on('open', () => this.onClientOpen()); - this.client.on('close', () => this.onClientClose()); - this.client.on('error', (error) => this.onClientError(error)); - }, 2000); -}; - - -HaEvents.prototype.startReconnectionLogic = function () { - + // TODO: Put in proper exponential retries + setTimeout(() => this.startListening(), 2000); }; module.exports = HaEvents; diff --git a/lib/node-home-assistant.js b/lib/node-home-assistant.js new file mode 100644 index 0000000..7da6bb3 --- /dev/null +++ b/lib/node-home-assistant.js @@ -0,0 +1,77 @@ +const debug = require('debug')('home-assistant'); +const HaEvents = require('./ha-events'); +const HaApi = require('./ha-api'); + +const DEFAULTS = { + baseUrl: null, + apiPass: null, + api: {}, + events: { + transport: 'sse', // For future support of websockets + retries: { + maxAttempts: 10, // How many times to retry connection + delay: 5000 // Delay this long before retry (in ms) + } + } +}; + +function HomeAssistant(config, { startListening } = {}) { + if (! (this instanceof HomeAssistant)) { return new HomeAssistant(config); } + + this.config = Object.assign({}, DEFAULTS, config); + this.api = new HaApi(this.config); + this.events = new HaEvents(this.config); + + if (startListening) { this.startListening(); } + return this; +} + +HomeAssistant.testConnection = async function ({ baseUrl, apiPass }) { + const apiTest = new HaApi({ baseUrl, apiPass }); + try { + await apiTest.getConfig(); + return true; + } catch (e) { + return false; + } +}; + +HomeAssistant.prototype.startListening = async function () { + if (!this.config.baseUrl) { throw new Error ('Home Assistant URL not set'); } + + try { + await HomeAssistant.testConnection(this.config) + } catch (e) { + debug(`Connection to home assistant could not be established with config: ${this.config.baseUrl} ${(this.config.apiPass) ? '' : ''}`); + throw e; + } + this.events.startListening(); + this.events.on('ha_events:state_changed', (evt) => this._onStateChanged(evt)); + return this; +} + +HomeAssistant.prototype.getStates = async function (entityId, forceRefresh = {}) { + if (!this.states || forceRefresh) { this.states = await this.api.getStates(); } + return (entityId) + ? this.states[entityId] || null + : this.states; +} + +HomeAssistant.prototype.getServices = async function ({ forceRefresh } = {}) { + if (!this.availableServices || forceRefresh) { this.availableServices = await this.api.getServices(); } + return this.availableServices; +}; + +HomeAssistant.prototype.getEvents = async function ({ forceRefresh } = {}) { + if (!this.availableEvents || forceRefresh) { this.availableEvents = await this.api.getEvents(); } + return this.availableEvents; +} + +HomeAssistant.prototype._onStateChanged = async function (changedEntity) { + debug(`changedEntity: ${JSON.stringify(changedEntity)}`); + const cachedEntity = await this.getStates(changedEntity.entity_id) + + if (!cachedEntity) { debug('Got state changed event for entity that was not know, this should not happen'); } + this.states[changedEntity.entity_id] = changedEntity.event.new_state; +}; +module.exports = HomeAssistant; diff --git a/package-lock.json b/package-lock.json index 3e7e765..82749b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -59,6 +59,12 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "assertion-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.0.2.tgz", + "integrity": "sha1-E8pRXYYgbaC6xm6DTdOX2HWBCUw=", + "dev": true + }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", @@ -106,6 +112,23 @@ "repeat-element": "1.1.2" } }, + "browser-stdout": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz", + "integrity": "sha1-81HTKWnTL6XXpVZxVCY9korjvR8=", + "dev": true + }, + "chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", + "integrity": "sha1-TQJjewZ/6Vi9v906QOxW/vc3Mkc=", + "dev": true, + "requires": { + "assertion-error": "1.0.2", + "deep-eql": "0.1.3", + "type-detect": "1.0.0" + } + }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", @@ -136,6 +159,12 @@ "readdirp": "2.1.0" } }, + "commander": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -172,12 +201,41 @@ "ms": "2.0.0" } }, + "deep-eql": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", + "integrity": "sha1-71WKyrjeJSBs1xOQbXTlaTDrafI=", + "dev": true, + "requires": { + "type-detect": "0.1.1" + }, + "dependencies": { + "type-detect": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-0.1.1.tgz", + "integrity": "sha1-C6XsKohWQORw6k6FBZcZANrFiCI=", + "dev": true + } + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", + "dev": true + }, "deep-extend": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", "dev": true }, + "diff": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.3.1.tgz", + "integrity": "sha512-MKPHZDMB0o6yHyDryUOScqZibp914ksXwAMYMTHj6KO8UeKsRYNJD3oNCKjTqZon+V488P7N/HzXF8t7ZR95ww==", + "dev": true + }, "duplexer": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", @@ -217,6 +275,18 @@ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true }, + "eslint-plugin-node": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-5.2.0.tgz", + "integrity": "sha512-N9FLFwknT5LhRhjz1lmHguNss/MCwkrLCS4CjqqTZZTJaUhLRfDNK3zxSHL/Il3Aa0Mw+xY3T1gtsJrUNoJy8Q==", + "dev": true, + "requires": { + "ignore": "3.3.5", + "minimatch": "3.0.4", + "resolve": "1.4.0", + "semver": "5.3.0" + } + }, "event-stream": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", @@ -315,6 +385,12 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, "fsevents": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.2.tgz", @@ -1084,14 +1160,6 @@ } } }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "5.0.1" - } - }, "string-width": { "version": "1.0.2", "bundled": true, @@ -1102,6 +1170,14 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, "stringstream": { "version": "0.0.5", "bundled": true, @@ -1214,6 +1290,20 @@ } } }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.3.3", + "path-is-absolute": "1.0.1" + } + }, "glob-base": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", @@ -1265,6 +1355,15 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, + "growl": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.2.tgz", + "integrity": "sha512-nidsnaoWVZIBLwA3sUIp3dA2DP2rT3dwEqINVacQ0+rZmc6UOwj2D729HTEjQYUKb+3wL9MeDbxpZtEiEJoUHQ==", + "dev": true, + "requires": { + "eslint-plugin-node": "5.2.0" + } + }, "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", @@ -1274,6 +1373,24 @@ "ansi-regex": "2.1.1" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "ignore": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.5.tgz", + "integrity": "sha512-JLH93mL8amZQhh/p6mfQgVBH3M6epNq3DfsXsTSuSrInVjwyYlFE1nv2AgfRCC8PoOhM0jwQ5v8s9LgbK7yGDw==", + "dev": true + }, "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -1292,6 +1409,16 @@ "integrity": "sha1-ReDi/3qesDCyfWK3SzdEt6esQhY=", "dev": true }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "1.3.3", + "wrappy": "1.0.2" + } + }, "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", @@ -1418,6 +1545,12 @@ "isarray": "1.0.0" } }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -1436,6 +1569,12 @@ "package-json": "1.2.0" } }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, "lodash._baseassign": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz", @@ -1588,6 +1727,44 @@ "minimist": "0.0.8" } }, + "mocha": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-4.0.0.tgz", + "integrity": "sha512-83e2QQWKbcBiPb1TuS80i4DxkpqQoOC9Y0TxOuML8NkzZWUkJJqWHAslhUS7x5nQcYMqnMwZDp5v3ABzV+ivCA==", + "dev": true, + "requires": { + "browser-stdout": "1.3.0", + "commander": "2.11.0", + "debug": "3.1.0", + "diff": "3.3.1", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.2", + "he": "1.1.1", + "mkdirp": "0.5.1", + "supports-color": "4.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "supports-color": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1609,6 +1786,23 @@ "inherits": "2.0.3" } }, + "nock": { + "version": "9.0.22", + "resolved": "https://registry.npmjs.org/nock/-/nock-9.0.22.tgz", + "integrity": "sha512-F5+Z5jhDourTtGIAEdqdtLhuAqO22Kg2rrvszgxwDPl8rMkw/pY0RJUHvFV/4bv1/oReZRAokMNGrUIQlKi/BQ==", + "dev": true, + "requires": { + "chai": "3.5.0", + "debug": "2.6.8", + "deep-equal": "1.0.1", + "json-stringify-safe": "5.0.1", + "lodash": "4.17.4", + "mkdirp": "0.5.1", + "propagate": "0.4.0", + "qs": "6.5.1", + "semver": "5.3.0" + } + }, "nodemon": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.11.0.tgz", @@ -1734,6 +1928,12 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "path-parse": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true + }, "pause-stream": { "version": "0.0.11", "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", @@ -1776,6 +1976,12 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, + "propagate": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/propagate/-/propagate-0.4.0.tgz", + "integrity": "sha1-8/zKCm/gZzanulcpZgaWF8EwtIE=", + "dev": true + }, "ps-tree": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.1.0.tgz", @@ -1785,6 +1991,12 @@ "event-stream": "3.3.4" } }, + "qs": { + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", + "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "dev": true + }, "querystringify": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-0.0.4.tgz", @@ -1939,6 +2151,15 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, + "resolve": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", + "integrity": "sha512-aW7sVKPufyHqOmyyLzg/J+8606v5nevBgaliIlV7nUpVMsDnoBGV/cbSLNjZAg9q0Cfd/+easKVKQ8vOu8fn1Q==", + "dev": true, + "requires": { + "path-parse": "1.0.5" + } + }, "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", @@ -1966,6 +2187,66 @@ "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", "dev": true }, + "should": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/should/-/should-13.1.0.tgz", + "integrity": "sha512-Nn354G2A2lnmDsKB5rQbSjdOA6XOYcdO8XysWOY7iGJMMsuQf3vo1yHcwjjczNUQJY7sKYlY/mAc2FoZpaGvDA==", + "dev": true, + "requires": { + "should-equal": "2.0.0", + "should-format": "3.0.3", + "should-type": "1.4.0", + "should-type-adaptors": "1.0.1", + "should-util": "1.0.0" + } + }, + "should-equal": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", + "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, + "requires": { + "should-type": "1.4.0" + } + }, + "should-format": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", + "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, + "requires": { + "should-type": "1.4.0", + "should-type-adaptors": "1.0.1" + } + }, + "should-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true + }, + "should-type-adaptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.0.1.tgz", + "integrity": "sha1-7+VVPN9oz/ZuXF9RtxLcNRx3vqo=", + "dev": true, + "requires": { + "should-type": "1.4.0", + "should-util": "1.0.0" + } + }, + "should-util": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.0.tgz", + "integrity": "sha1-yYzaN0qmsZDfi6h8mInCtNtiAGM=", + "dev": true + }, + "simple-mock": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/simple-mock/-/simple-mock-0.8.0.tgz", + "integrity": "sha1-ScmiI/pu6o4sT9aUj+gwDNillPM=", + "dev": true + }, "slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -1996,15 +2277,6 @@ "integrity": "sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-length": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz", @@ -2014,6 +2286,15 @@ "strip-ansi": "3.0.1" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -2056,6 +2337,12 @@ "nopt": "1.0.10" } }, + "type-detect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-1.0.0.tgz", + "integrity": "sha1-diIXzAbbJY7EiQihKY6LlRIejqI=", + "dev": true + }, "undefsafe": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-0.0.3.tgz", diff --git a/package.json b/package.json index 272607b..083bc64 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,9 @@ "version": "0.1.0", "main": "index.js", "scripts": { - "dev:watch": "DEBUG=home-assistant* nodemon _scratchpad/scratchpad.js" + "dev:watch": "DEBUG=home-assistant* nodemon _scratchpad/scratchpad.js", + "test": "DEBUG=home-assistant* mocha __tests__/**/*.spec.js", + "test:watch": "DEBUG=home-assistant* mocha --watch --inspect __tests__/**/*.spec.js" }, "license": "MIT", "dependencies": { @@ -12,6 +14,10 @@ "eventsource": "^0.2.2" }, "devDependencies": { - "nodemon": "^1.11.0" + "mocha": "^4.0.0", + "nock": "^9.0.22", + "nodemon": "^1.11.0", + "should": "^13.1.0", + "simple-mock": "^0.8.0" } }