diff --git a/.narval.yml b/.narval.yml index 017a23b..a0f14ef 100644 --- a/.narval.yml +++ b/.narval.yml @@ -1,3 +1,67 @@ +schemas: + bind: &bind + - lib + - test + - server.js + - bin + coverage: &disable-coverage + enabled: false + envs: + local-service: &local-service-env + service_port: 3000 + service_host_name: localhost + domapic_path: .test + service_extra_options: --gpio=18 --debounce=3000 + docker-service: &docker-service-env + service_port: 3000 + service_host_name: module-container + domapic_path: .shared + service_extra_options: --gpio=18 --debounce=3000 + clean: &clean + local: + command: test/functional/commands/local-clean.sh + docker: + container: service-container + command: test/functional/commands/clean.sh + down-volumes: true + services: + local: &local-service + command: test/functional/commands/start-module.sh + env: *local-service-env + docker: &docker-service + container: module-container + command: test/functional/commands/start-module.sh + env: *docker-service-env + local-cli: &local-service-cli + <<: *local-service + command: test/functional/commands/start-module-cli.sh + docker-cli: &docker-service-cli + <<: *docker-service + command: test/functional/commands/start-module-cli.sh + test: &functional-test + local: + wait-on: tcp:localhost:3000 + env: *local-service-env + docker: + container: test-container + wait-on: tcp:module-container:3000 + env: *docker-service-env +docker-images: + - name: node-image + from: node:8.11.1 + expose: + - 3000 + add: + - package.json + - npm-shrinkwrap.json + install: test/functional/commands/install.sh +docker-containers: + - name: module-container + build: node-image + bind: *bind + - name: test-container + build: node-image + bind: *bind suites: unit: - name: unit @@ -6,3 +70,32 @@ suites: coverage: config: dir: .coverage + functional: + - name: api-and-config + describe: should init the contact sensor with provided configuration, and api should work as expected + before: *clean + services: + - name: module + abort-on-error: true + local: *local-service + docker: *docker-service + test: + <<: *functional-test + specs: + - test/functional/specs/api.specs.js + - test/functional/specs/config.specs.js + coverage: *disable-coverage + - name: api-and-config-cli + describe: should init the contact sensor with provided configuration, and api should work as expected when started using cli + before: *clean + services: + - name: module + abort-on-error: true + local: *local-service-cli + docker: *docker-service-cli + test: + <<: *functional-test + specs: + - test/functional/specs/api.specs.js + - test/functional/specs/config.specs.js + coverage: *disable-coverage diff --git a/README.md b/README.md index 85dda4e..41e67d6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,123 @@ -# contact-sensor-domapic-module +# Contact Sensor Domapic Module + +> Domapic module for handling a contact sensor + +[![Build status][travisci-image]][travisci-url] [![Coverage Status][coveralls-image]][coveralls-url] [![Quality Gate][quality-gate-image]][quality-gate-url] [![js-standard-style][standard-image]][standard-url] + +[![NPM dependencies][npm-dependencies-image]][npm-dependencies-url] [![Last commit][last-commit-image]][last-commit-url] [![Last release][release-image]][release-url] + +[![NPM downloads][npm-downloads-image]][npm-downloads-url] [![License][license-image]][license-url] + +--- + +## Intro + +This package starts a Domapic Module with an ability called "contactSensor" that handles a GPIO in "in" mode. It is intended to be used in a Raspberry Pi or any other system supporting the [onoff][onoff-url] library, such as C.H.I.P. or BeagleBone. + +It can be used alone, but also can be connected to a [Domapic Controller][domapic-controller-url] to get the most out of it. + +![Contact sensor connection schema][contact-sensor-schema-image] + +## Installation + +```bash +npm i contact-sensor-domapic-module -g +``` + +## Start the server + +```bash +domapic-contact-sensor start --gpio=12 --debounce=3000 --save +``` + +The server will be started in background using [pm2][pm2-url]. + +To display logs, type: + +```bash +domapic-contact-sensor logs #--lines=300 +``` + +## Options + +The module, apart of all common [domapic services options][domapic-service-options-url], provides custom options for configuring the sensor: + +* `gpio` - Number defining the Gpio where the contact sensor to be controlled is connected. +* `debounce` - Time in miliseconds to wait for before notifying about a change in the status of the contact sensor. + +## Connection with Domapic Controller + +Connect the module with a Domapic Controller providing the Controller url and connection token (you'll find it the Controller logs when it is started): + +```bash +domapic-contact-sensor start --controller=http://192.168.1.110:3000 --controllerApiKey=fo--controller-api-key +``` + +Now, the module can be controlled through the Controller interface, or installed plugins. + +## Stand alone usage + +Domapic modules are intended to be used through Domapic Controller, but can be used as an stand-alone service as well. Follow next instructions to use the built-in api by your own: + +### Rest API + +When the server is started, you can browse to the provided Swagger interface to get all the info about the api resources. Apart of all api methods common to all [Domapic Services][domapic-service-url], the server provides one [_Domapic Ability_][domapic-service-abilities-url] for getting the state of the sendor, which generates one extra API resource: + +* `/api/abilities/contact-sensor/state` - Returns the current state of the sensor. + +### Authentication + +The server includes the [Domapic Services][domapic-service-url] authentication method, which is disabled by default for `127.0.0.1`. +You can disable the authentication using the `--authDisabled` option (not recommended if your server is being exposed to the Internet). Read more about [available options in the domapic services documentation][domapic-service-options-url]. + +If you want to authenticate when requesting from another IPs, look for the api key automatically generated and intended to be used by Domapic Controller when the server is started. You'll find it in the server logs: + +``` +----------------------------------------------------------------- +Try adding connection from Controller, using the next service Api Key: HMl6GHWr7foowxM40CB6tQPuXt3zc7zE +----------------------------------------------------------------- +``` + +To make your own requests to the api, provide this token using the `X-Api-Key` header. + +Use the mentioned api key also for authenticating when using the Swagger interface. + +## Alternative command line methods + +### Not global installation + +If the package is not installed globally, you can replace the `domapic-contact-sensor` command in examples above by `npm run domapic-contact-sensor --` (commands must be executed inside the package folder in that case) + +### Not background mode + +If you don't want to use the built-in background runner, you can start the server directly, attaching logs to current `stdout`. Move to the package folder and replace the `domapic-contact-sensor` command of examples above by `node server.js`. Press `CTRL+C` to stop the server. + + +[coveralls-image]: https://coveralls.io/repos/github/javierbrea/contact-sensor-domapic-module/badge.svg?branch=master +[coveralls-url]: https://coveralls.io/github/javierbrea/contact-sensor-domapic-module +[travisci-image]: https://travis-ci.com/javierbrea/contact-sensor-domapic-module.svg?branch=master +[travisci-url]: https://travis-ci.com/javierbrea/contact-sensor-domapic-module +[last-commit-image]: https://img.shields.io/github/last-commit/javierbrea/contact-sensor-domapic-module.svg +[last-commit-url]: https://github.com/javierbrea/contact-sensor-domapic-module/commits +[license-image]: https://img.shields.io/npm/l/contact-sensor-domapic-module.svg +[license-url]: https://github.com/javierbrea/contact-sensor-domapic-module/blob/master/LICENSE +[npm-downloads-image]: https://img.shields.io/npm/dm/contact-sensor-domapic-module.svg +[npm-downloads-url]: https://www.npmjs.com/package/contact-sensor-domapic-module +[npm-dependencies-image]: https://img.shields.io/david/javierbrea/contact-sensor-domapic-module.svg +[npm-dependencies-url]: https://david-dm.org/javierbrea/contact-sensor-domapic-module +[quality-gate-image]: https://sonarcloud.io/api/project_badges/measure?project=contact-sensor-domapic-module&metric=alert_status +[quality-gate-url]: https://sonarcloud.io/dashboard?id=contact-sensor-domapic-module +[release-image]: https://img.shields.io/github/release-date/javierbrea/contact-sensor-domapic-module.svg +[release-url]: https://github.com/javierbrea/contact-sensor-domapic-module/releases +[standard-image]: https://img.shields.io/badge/code%20style-standard-brightgreen.svg +[standard-url]: http://standardjs.com/ + +[onoff-url]: https://www.npmjs.com/package/onoff +[domapic-controller-url]: https://www.npmjs.com/package/domapic-controller +[domapic-service-options-url]: https://github.com/domapic/domapic-service#options +[domapic-service-abilities-url]: https://github.com/domapic/domapic-service#abilities +[domapic-service-url]: https://github.com/domapic/domapic-service +[pm2-url]: http://pm2.keymetrics.io/ + +[contact-sensor-schema-image]: http://domapic.com/assets/contact-sensor/fritz_schema.png -Domapic module for handling a contact sensor diff --git a/lib/cli.js b/lib/cli.js index e69de29..0a69cc2 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -0,0 +1,12 @@ +'use strict' + +const path = require('path') +const domapic = require('domapic-service') + +const options = require('./options') + +domapic.cli({ + packagePath: path.resolve(__dirname, '..'), + script: path.resolve(__dirname, '..', 'server.js'), + customConfig: options +}) diff --git a/lib/options.js b/lib/options.js new file mode 100644 index 0000000..358ac4b --- /dev/null +++ b/lib/options.js @@ -0,0 +1,15 @@ +'use strict' + +const { GPIO_OPTION, DEBOUNCE_OPTION } = require('./statics') + +module.exports = { + [GPIO_OPTION]: { + type: 'number', + describe: 'Set gpio number for the sensor' + }, + [DEBOUNCE_OPTION]: { + type: 'number', + describe: 'Set debounce timeout for the sensor', + default: 1000 + } +} diff --git a/lib/statics.js b/lib/statics.js new file mode 100644 index 0000000..9f6b2c5 --- /dev/null +++ b/lib/statics.js @@ -0,0 +1,19 @@ +'use strict' + +const ABILITY_NAME = 'contactSensor' +const DEBOUNCE_OPTION = 'debounce' +const GPIO_OPTION = 'gpio' + +const ABILITY_DESCRIPTION = 'Contact sensor status' +const ABILITY_STATE_DESCRIPTION = 'Returns current contact sensor status' +const ABILITY_EVENT_DESCRIPTION = 'Contact sensor status has changed' + +module.exports = { + ABILITY_NAME, + DEBOUNCE_OPTION, + GPIO_OPTION, + + ABILITY_DESCRIPTION, + ABILITY_STATE_DESCRIPTION, + ABILITY_EVENT_DESCRIPTION +} diff --git a/package-lock.json b/npm-shrinkwrap.json similarity index 99% rename from package-lock.json rename to npm-shrinkwrap.json index 360c395..1733e31 100644 --- a/package-lock.json +++ b/npm-shrinkwrap.json @@ -628,6 +628,11 @@ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.12.0.tgz", "integrity": "sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg==" }, + "bindings": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", + "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==" + }, "blessed": { "version": "0.1.81", "resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz", @@ -1154,6 +1159,11 @@ "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, + "death": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", + "integrity": "sha1-AaqcQB7dknUFFEcLgmY5DGbGcxg=" + }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1442,6 +1452,15 @@ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, + "epoll": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/epoll/-/epoll-2.0.6.tgz", + "integrity": "sha512-CtTAsX+Q+mpz6VHVtOSzbempais+FoDyrM6vBtvp+322YB93os/ZucdV8LTP4J1jZxaO1TPMSxWGKglc2P4PHA==", + "requires": { + "bindings": "^1.3.0", + "nan": "^2.11.1" + } + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2831,6 +2850,15 @@ "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", "dev": true }, + "gpio-in-domapic": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/gpio-in-domapic/-/gpio-in-domapic-1.0.0.tgz", + "integrity": "sha512-SNxYRKpSYlNcsgwgTul0YbTienUy3PU9xhIrzvaHXkH0/QRXmjqSEjmOeF7CYVYg6cP9JAszwUDNe2LQ0qYaMA==", + "requires": { + "death": "1.1.0", + "onoff": "3.2.2" + } + }, "graceful-fs": { "version": "4.1.15", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", @@ -4178,12 +4206,6 @@ "sinon-chai": "3.2.0" } }, - "mockery": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mockery/-/mockery-2.1.0.tgz", - "integrity": "sha512-9VkOmxKlWXoDO/h1jDZaS4lH33aWfRiJiNT/tKj+8OGzrcFDLo8d0syGdbsc3Bc4GvRXPb+NMMvojotmuGJTvA==", - "dev": true - }, "moment": { "version": "2.23.0", "resolved": "https://registry.npmjs.org/moment/-/moment-2.23.0.tgz", @@ -4210,8 +4232,7 @@ "nan": { "version": "2.11.1", "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", - "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", - "optional": true + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==" }, "nanomatch": { "version": "1.2.13", @@ -4507,6 +4528,15 @@ "mimic-fn": "^1.0.0" } }, + "onoff": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/onoff/-/onoff-3.2.2.tgz", + "integrity": "sha512-wLxeB03QLSsbIhIqR24Ia137uyCKNR7O8SEnUhN4kmsrDrhZNVpSWfOC7Tp1bvz7qKLwfYICGYbPMjzun4opLg==", + "requires": { + "epoll": "^2.0.4", + "lodash.debounce": "^4.0.8" + } + }, "openapi-jsonschema-parameters": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/openapi-jsonschema-parameters/-/openapi-jsonschema-parameters-1.0.3.tgz", @@ -5357,6 +5387,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.2.tgz", "integrity": "sha1-0epG1lSm7k+O5qT+oQGMIpEZBLQ=", + "dev": true, "requires": { "bluebird": "^3.5.0", "request-promise-core": "1.1.1", @@ -5368,6 +5399,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "dev": true, "requires": { "lodash": "^4.13.1" } @@ -5934,7 +5966,8 @@ "stealthy-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=" + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true }, "string-width": { "version": "2.1.1", diff --git a/package.json b/package.json index b2205ee..94d4c60 100644 --- a/package.json +++ b/package.json @@ -13,13 +13,13 @@ }, "dependencies": { "domapic-service": "1.0.0-alpha.4", - "request": "2.88.0", - "request-promise": "4.2.2" + "gpio-in-domapic": "1.0.0" }, "devDependencies": { "coveralls": "3.0.2", "narval": "2.2.1", - "mockery": "2.1.0" + "request": "2.88.0", + "request-promise": "4.2.2" }, "files": [ "/lib", diff --git a/server.js b/server.js index ccacec3..cf94eb5 100644 --- a/server.js +++ b/server.js @@ -1 +1,51 @@ 'use strict' + +const path = require('path') + +const domapic = require('domapic-service') +const gpioIn = require('gpio-in-domapic') + +const { + ABILITY_NAME, + DEBOUNCE_OPTION, + ABILITY_DESCRIPTION, + ABILITY_STATE_DESCRIPTION, + ABILITY_EVENT_DESCRIPTION +} = require('./lib/statics') + +const options = require('./lib/options') + +domapic.createModule({ + packagePath: path.resolve(__dirname), + customConfig: options +}).then(async dmpcModule => { + const contactSensor = new gpioIn.Gpio(dmpcModule, { + }, { + debounceTimeout: DEBOUNCE_OPTION + }) + + await dmpcModule.register({ + [ABILITY_NAME]: { + description: ABILITY_DESCRIPTION, + data: { + type: 'boolean' + }, + state: { + description: ABILITY_STATE_DESCRIPTION, + handler: () => contactSensor.status + }, + event: { + description: ABILITY_EVENT_DESCRIPTION + } + } + }) + + await contactSensor.init() + + contactSensor.events.on(gpioIn.Gpio.eventNames.CHANGE, newValue => { + dmpcModule.tracer.debug(ABILITY_EVENT_DESCRIPTION, newValue) + dmpcModule.events.emit(ABILITY_NAME, newValue) + }) + + return dmpcModule.start() +}) diff --git a/test/functional/commands/clean.sh b/test/functional/commands/clean.sh new file mode 100755 index 0000000..d616a3a --- /dev/null +++ b/test/functional/commands/clean.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +rm -rf .test +mkdir .test diff --git a/test/functional/commands/install.sh b/test/functional/commands/install.sh new file mode 100755 index 0000000..c608738 --- /dev/null +++ b/test/functional/commands/install.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +npm i diff --git a/test/functional/commands/local-clean.sh b/test/functional/commands/local-clean.sh new file mode 100755 index 0000000..606641a --- /dev/null +++ b/test/functional/commands/local-clean.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +./test/functional/commands/pm2-clean.sh +./test/functional/commands/clean.sh diff --git a/test/functional/commands/pm2-clean.sh b/test/functional/commands/pm2-clean.sh new file mode 100755 index 0000000..ffc6ebd --- /dev/null +++ b/test/functional/commands/pm2-clean.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +pm2 flush +pm2 delete domapic-contact-sensor diff --git a/test/functional/commands/start-module-cli.sh b/test/functional/commands/start-module-cli.sh new file mode 100755 index 0000000..ada6cc4 --- /dev/null +++ b/test/functional/commands/start-module-cli.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash + +npm run domapic-contact-sensor start -- --path=${domapic_path} ${service_extra_options} +npm run domapic-contact-sensor logs diff --git a/test/functional/commands/start-module.sh b/test/functional/commands/start-module.sh new file mode 100755 index 0000000..11ac90e --- /dev/null +++ b/test/functional/commands/start-module.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash + +node server.js --path=${domapic_path} ${service_extra_options} diff --git a/test/functional/specs/api.specs.js b/test/functional/specs/api.specs.js new file mode 100644 index 0000000..ef7f187 --- /dev/null +++ b/test/functional/specs/api.specs.js @@ -0,0 +1,27 @@ +const test = require('narval') + +const utils = require('./utils') + +test.describe('contact sensor api', function () { + this.timeout(10000) + let connection + + test.before(() => { + connection = new utils.Connection() + }) + + test.describe('contact sensor state', () => { + test.it('should return false', () => { + return connection.request('/abilities/contact-sensor/state', { + method: 'GET' + }).then(response => { + return Promise.all([ + test.expect(response.statusCode).to.equal(200), + test.expect(response.body).to.deep.equal({ + data: false + }) + ]) + }) + }) + }) +}) diff --git a/test/functional/specs/config.specs.js b/test/functional/specs/config.specs.js new file mode 100644 index 0000000..6abaf79 --- /dev/null +++ b/test/functional/specs/config.specs.js @@ -0,0 +1,24 @@ +const test = require('narval') + +const utils = require('./utils') + +test.describe('module configuration', function () { + this.timeout(10000) + let connection + + test.before(() => { + connection = new utils.Connection() + }) + + test.it('should be exposed in config api', () => { + return connection.request('/config', { + method: 'GET' + }).then(response => { + return Promise.all([ + test.expect(response.statusCode).to.equal(200), + test.expect(response.body.debounce).to.equal(3000), + test.expect(response.body.gpio).to.equal(18) + ]) + }) + }) +}) diff --git a/test/functional/specs/utils.js b/test/functional/specs/utils.js new file mode 100644 index 0000000..5370e74 --- /dev/null +++ b/test/functional/specs/utils.js @@ -0,0 +1,82 @@ +'use strict' + +const path = require('path') +const fs = require('fs') +const testUtils = require('narval/utils') + +const requestPromise = require('request-promise') + +const SERVICE_HOST = process.env.service_host_name +const SERVICE_PORT = process.env.service_port +const DOMAPIC_PATH = process.env.domapic_path +const ESTIMATED_START_TIME = 1000 + +const readFile = filePath => new Promise((resolve, reject) => { + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + reject(err) + } else { + resolve(data) + } + }) +}) + +const waitOn = (time = ESTIMATED_START_TIME) => new Promise(resolve => { + setTimeout(() => { + resolve() + }, time) +}) + +const request = (uri, options = {}) => { + const defaultOptions = { + uri: `http://${SERVICE_HOST}:${SERVICE_PORT}/api${uri}`, + json: true, + strictSSL: false, + rejectUnauthorized: false, + simple: false, + requestCert: false, + resolveWithFullResponse: true + } + return requestPromise(Object.assign(defaultOptions, options)) +} + +const readStorage = () => readFile(path.resolve(__dirname, '..', '..', '..', DOMAPIC_PATH, '.domapic', 'contact-sensor-domapic-module', 'storage', 'service.json')) + .then(data => Promise.resolve(JSON.parse(data))) + +const controllerApiKey = (property, value) => readStorage() + .then(data => Promise.resolve(data.apiKeys.find(apiKeyData => apiKeyData.user === 'controller'))) + +class Connection { + constructor () { + this._apiKey = null + } + + async request (uri, options = {}) { + const method = options.method || 'GET' + if (!this._apiKey) { + this._apiKey = await controllerApiKey() + } + return request(uri, + { + headers: { + 'X-Api-Key': this._apiKey.key + }, + method, + ...options + } + ) + } +} + +const moduleLogs = (time = 200) => waitOn(time) + .then(() => testUtils.logs.combined('module')) + +module.exports = { + waitOn, + readStorage, + Connection, + DOMAPIC_PATH, + SERVICE_HOST, + SERVICE_PORT, + moduleLogs +} diff --git a/test/unit/Domapic.mocks.js b/test/unit/Domapic.mocks.js new file mode 100644 index 0000000..f229283 --- /dev/null +++ b/test/unit/Domapic.mocks.js @@ -0,0 +1,55 @@ +const test = require('narval') + +const domapicService = require('domapic-service') + +const Mock = function () { + let sandbox = test.sinon.createSandbox() + let resolveStartCalled + + const resolveOnStartCalledPromise = new Promise(resolve => { + resolveStartCalled = resolve + }) + + const moduleStubs = { + start: sandbox.stub().callsFake(() => { + resolveStartCalled() + return Promise.resolve() + }), + register: sandbox.stub(), + events: { + emit: sandbox.stub() + }, + config: { + get: sandbox.stub().resolves() + }, + tracer: { + info: sandbox.stub().resolves(), + debug: sandbox.stub().resolves() + }, + errors: { + BadGateway: sandbox.stub() + } + } + + const stubs = { + createModule: sandbox.stub(domapicService, 'createModule').resolves(moduleStubs), + cli: sandbox.stub(domapicService, 'cli').resolves(moduleStubs) + } + + const restore = () => { + sandbox.restore() + } + + return { + restore, + stubs: { + ...stubs, + module: moduleStubs + }, + utils: { + resolveOnStartCalled: () => resolveOnStartCalledPromise + } + } +} + +module.exports = Mock diff --git a/test/unit/GpioIn.mocks.js b/test/unit/GpioIn.mocks.js new file mode 100644 index 0000000..50a3a42 --- /dev/null +++ b/test/unit/GpioIn.mocks.js @@ -0,0 +1,42 @@ +const test = require('narval') + +const gpioIn = require('gpio-in-domapic') + +const Mock = function () { + let sandbox = test.sinon.createSandbox() + let eventListener + + const stubs = { + init: sandbox.stub().resolves(), + events: { + on: sandbox.stub().callsFake((eventName, cb) => { + eventListener = cb + }) + } + } + + const Constructor = sandbox.stub(gpioIn, 'Gpio').callsFake(function () { + return stubs + }) + + Constructor.eventNames = { + CHANGE: 'change' + } + + const restore = () => { + sandbox.restore() + } + + return { + restore, + stubs: { + Constructor, + instance: stubs + }, + utils: { + getEventListener: () => eventListener + } + } +} + +module.exports = Mock diff --git a/test/unit/lib/cli.js b/test/unit/lib/cli.js index 4286b56..ea2a931 100644 --- a/test/unit/lib/cli.js +++ b/test/unit/lib/cli.js @@ -1,7 +1,36 @@ +const path = require('path') + const test = require('narval') +const DomapicMocks = require('../Domapic.mocks') +const options = require('../../../lib/options') + test.describe('cli', () => { - test.it('should exist', () => { + const packagePath = path.resolve(__dirname, '..', '..', '..') + let domapic + + test.before(() => { + domapic = new DomapicMocks() require('../../../lib/cli') }) + + test.after(() => { + domapic.restore() + }) + + test.it('should have created a Domapic cli', () => { + test.expect(domapic.stubs.cli).to.have.been.called() + }) + + test.it('should have passed the package path to the cli', () => { + test.expect(domapic.stubs.cli.getCall(0).args[0].packagePath).to.equal(packagePath) + }) + + test.it('should have passed the server path to the cli', () => { + test.expect(domapic.stubs.cli.getCall(0).args[0].script).to.equal(path.resolve(packagePath, 'server.js')) + }) + + test.it('should have passed the options to the cli', () => { + test.expect(domapic.stubs.cli.getCall(0).args[0].customConfig).to.equal(options) + }) }) diff --git a/test/unit/server.spec.js b/test/unit/server.spec.js index f0e2532..afe3629 100644 --- a/test/unit/server.spec.js +++ b/test/unit/server.spec.js @@ -1,7 +1,68 @@ +const path = require('path') + const test = require('narval') +const DomapicMocks = require('./Domapic.mocks') +const GpioInMocks = require('./GpioIn.mocks') + test.describe('server', () => { - test.it('should exist', () => { + const fooConfig = { + gpio: 12, + debounce: 300 + } + let domapic + let gpioIn + let abilities + + test.before(async () => { + domapic = new DomapicMocks() + gpioIn = new GpioInMocks() + domapic.stubs.module.config.get.resolves(fooConfig) require('../../server') + await domapic.utils.resolveOnStartCalled() + }) + + test.after(() => { + gpioIn.restore() + domapic.restore() + }) + + test.it('should have created a Domapic Module, passing the package path', () => { + test.expect(domapic.stubs.createModule.getCall(0).args[0].packagePath).to.equal(path.resolve(__dirname, '..', '..')) + }) + + test.it('should have called to start the gpio', () => { + test.expect(gpioIn.stubs.instance.init).to.have.been.called() + }) + + test.it('should have called to start the server', () => { + test.expect(domapic.stubs.module.start).to.have.been.called() + }) + + test.describe('when domapic module is returned', () => { + test.it('should have created a domapic gpio in', () => { + test.expect(gpioIn.stubs.Constructor).to.have.been.calledWith(domapic.stubs.module, {}, { + debounceTimeout: 'debounce' + }) + }) + + test.it('should have registered abilities', () => { + abilities = domapic.stubs.module.register.getCall(0).args[0] + test.expect(domapic.stubs.module.register).to.have.been.called() + }) + }) + + test.describe('contactSensor state handler', () => { + test.it('should return current gpio status', () => { + gpioIn.stubs.instance.status = true + test.expect(abilities.contactSensor.state.handler()).to.equal(true) + }) + }) + + test.describe('contactSensor events listener', () => { + test.it('should emit a domapic event', () => { + gpioIn.utils.getEventListener()(true) + test.expect(domapic.stubs.module.events.emit).to.have.been.calledWith('contactSensor', true) + }) }) })