├── .dockerignore ├── .env ├── .gitignore ├── CHANGELOG.md ├── Dockerfile ├── LICENSE.md ├── Makefile ├── Procfile ├── README.md ├── app.js ├── app └── models │ └── devices │ └── device.js ├── deploy ├── deploy.conf ├── lib ├── authorizer.js └── server.js ├── package.json └── test ├── app_spec.js ├── factories └── devices │ └── device.js └── mocha.opts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | .tmp 4 | .env 5 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | NODE_ENV=development 2 | REDIS_HOST=localhost 3 | MONGOLAB_DEVICES_URL=mongodb://localhost:27017/devices_development 4 | DEBUG=lelylan 5 | NODE_PORT=1883 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile ~/.gitignore_global 6 | 7 | # Ignore temporary files 8 | *.swp 9 | *.swo 10 | *.DS_Store 11 | 12 | # Ignore dependency libraries 13 | node_modules 14 | 15 | # Ignore logs 16 | *.log 17 | 18 | # Procfile env 19 | .production.env 20 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.1.0 (2016.01.01) 4 | 5 | First version MQTT server for Lelylan. 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:0.10 2 | MAINTAINER Federico Gonzalez 3 | 4 | RUN apt-get update -qq \ 5 | && apt-get install -y libzmq3 libzmq3-dev build-essential make \ 6 | && apt-get clean \ 7 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 8 | 9 | RUN npm config set registry http://registry.npmjs.org 10 | RUN mkdir -p /usr/src/app 11 | WORKDIR /usr/src/app 12 | 13 | RUN npm install -g foreman && npm cache clean 14 | ADD package.json /usr/src/app/ 15 | RUN npm install && npm cache clean 16 | ADD . /usr/src/app 17 | 18 | EXPOSE 1883 19 | 20 | CMD [ "nf", "start" ] 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Lelylan 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE_ENV:=development 2 | MONGOLAB_JOBS_HOST:=mongodb://localhost:27017 3 | MONGOLAB_JOBS_DB:=jobs_test 4 | MONGOLAB_DEVICES_URL:=mongodb://localhost:27017/devices_test 5 | ENV:=NODE_ENV=${NODE_ENV} MONGOLAB_JOBS_HOST=${MONGOLAB_JOBS_HOST} MONGOLAB_JOBS_DB=${MONGOLAB_JOBS_DB} MONGOLAB_DEVICES_URL=${MONGOLAB_DEVICES_URL} 6 | 7 | test: 8 | ${ENV} ./node_modules/.bin/mocha --recursive test 9 | 10 | bail: 11 | ${ENV} ./node_modules/.bin/mocha --recursive test --bail --reporter spec 12 | 13 | ci: 14 | ${ENV} ./node_modules/.bin/mocha --recursive --watch test 15 | 16 | .PHONY: test 17 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node app.js 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lelylan MQTT Server/Broker 2 | 3 | ## Requirements 4 | 5 | The MQTT server/broker is tested against Node 0.10.36 6 | 7 | 8 | ## Getting Started 9 | 10 | ```bash 11 | $ git clone git@github.com:lelylan/mqtt.git && cd mqtt 12 | $ npm install && npm install -g foreman 13 | $ nf start 14 | ``` 15 | 16 | ## Install with docker 17 | 18 | #### Badges 19 | Docker image: [lelylanlab/mqtt](https://hub.docker.com/r/lelylanlab/mqtt/) 20 | 21 | [![](https://images.microbadger.com/badges/version/lelylanlab/mqtt:latest.svg)](http://microbadger.com/images/lelylanlab/mqtt:latest "Get your own version badge on microbadger.com") [![](https://images.microbadger.com/badges/image/lelylanlab/mqtt:latest.svg)](http://microbadger.com/images/lelylanlab/mqtt:latest "Get your own image badge on microbadger.com") 22 | 23 | ### Use docker hub image 24 | ```bash 25 | $ docker run -d -it --name mqtt lelylanlab/mqtt 26 | ``` 27 | 28 | ### Generate local image 29 | ```bash 30 | $ docker build --tag=mqtt . 31 | $ docker run -d -it --name mqtt mqtt 32 | ``` 33 | 34 | When installing the service in production set [lelylan environment variables](https://github.com/lelylan/lelylan/blob/master/README.md#production). 35 | 36 | 37 | ## Resources 38 | 39 | * [Lelylan MQTT Documentation](http://dev.lelylan.com/api#api-physical-mqtt) 40 | * [How to Build an High Availability MQTT Cluster for the Internet of Things](https://medium.com/@lelylan/how-to-build-an-high-availability-mqtt-cluster-for-the-internet-of-things-8011a06bd000) 41 | 42 | 43 | ## Contributing 44 | 45 | Fork the repo on github and send a pull requests with topic branches. 46 | Do not forget to provide specs to your contribution. 47 | 48 | 49 | ### Running specs 50 | 51 | ```bash 52 | $ npm install 53 | $ npm test 54 | ``` 55 | 56 | ## Coding guidelines 57 | 58 | Follow [Felix](http://nodeguide.com/style.html) guidelines. 59 | 60 | 61 | ## Feedback 62 | 63 | Use the [issue tracker](http://github.com/lelylan/mqtt/issues) for bugs or [stack overflow](http://stackoverflow.com/questions/tagged/lelylan) for questions. 64 | [Mail](mailto:dev@lelylan.com) or [Tweet](http://twitter.com/lelylan) us for any idea that can improve the project. 65 | 66 | 67 | ## Links 68 | 69 | * [GIT Repository](http://github.com/lelylan/mqtt) 70 | * [Lelylan Dev Center](http://dev.lelylan.com) 71 | * [Lelylan Site](http://lelylan.com) 72 | 73 | 74 | ## Authors 75 | 76 | [Andrea Reginato](https://www.linkedin.com/in/andreareginato) 77 | 78 | 79 | ## Contributors 80 | 81 | Special thanks to all [contributors](https://github.com/lelylan/mqtt/contributors) 82 | for submitting patches. 83 | 84 | 85 | ## Changelog 86 | 87 | See [CHANGELOG](https://github.com/lelylan/mqtt/blob/master/CHANGELOG.md) 88 | 89 | 90 | ## License 91 | 92 | Lelylan is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0). 93 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | var server = require('./lib/server') 2 | , debug = require('debug')('lelylan'); 3 | 4 | var ascoltatore = { 5 | type: 'redis', 6 | redis: require('redis'), 7 | db: 12, 8 | port: 6379, 9 | host: process.env.REDIS_HOST } 10 | , settings = { 11 | port: process.env.NODE_PORT || 1883, 12 | backend: ascoltatore }; 13 | 14 | var app = new server.start(settings); 15 | 16 | app.on('published', function(packet, client) { 17 | if (packet.topic.indexOf('$SYS') === 0) return; // doesn't print stats info 18 | debug('ON PUBLISHED', packet.payload.toString(), 'on topic', packet.topic); 19 | }); 20 | 21 | app.on('ready', function() { 22 | debug('MQTT Server listening on port', process.env.NODE_PORT) 23 | }); 24 | 25 | -------------------------------------------------------------------------------- /app/models/devices/device.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , db = mongoose.connect(process.env.MONGOLAB_DEVICES_URL); 3 | 4 | var devicesSchema = new mongoose.Schema({ 5 | secret: String 6 | }); 7 | 8 | module.exports = db.model('device', devicesSchema); 9 | -------------------------------------------------------------------------------- /deploy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION="0.4.0" 4 | CONFIG=./deploy.conf 5 | LOG=/tmp/deploy.log 6 | TEST=1 7 | REF= 8 | ENV= 9 | 10 | # 11 | # Output usage information. 12 | # 13 | 14 | usage() { 15 | cat <<-EOF 16 | 17 | Usage: deploy [options] [command] 18 | 19 | Options: 20 | 21 | -C, --chdir change the working directory to 22 | -c, --config set config path. defaults to ./deploy.conf 23 | -T, --no-tests ignore test hook 24 | -V, --version output program version 25 | -h, --help output help information 26 | 27 | Commands: 28 | 29 | setup run remote setup commands 30 | update update deploy to the latest release 31 | revert [n] revert to [n]th last deployment or 1 32 | config [key] output config file or [key] 33 | curr[ent] output current release commit 34 | prev[ious] output previous release commit 35 | exec|run execute the given 36 | console open an ssh session to the host 37 | list list previous deploy commits 38 | [ref] deploy to [ref], the 'ref' setting, or latest tag 39 | 40 | EOF 41 | } 42 | 43 | # 44 | # Abort with 45 | # 46 | 47 | abort() { 48 | echo 49 | echo " $@" 1>&2 50 | echo 51 | exit 1 52 | } 53 | 54 | # 55 | # Log . 56 | # 57 | 58 | log() { 59 | echo " ○ $@" 60 | } 61 | 62 | # 63 | # Set configuration file . 64 | # 65 | 66 | set_config_path() { 67 | test -f $1 || abort invalid --config path 68 | CONFIG=$1 69 | } 70 | 71 | # 72 | # Check if config
exists. 73 | # 74 | 75 | config_section() { 76 | grep "^\[$1" $CONFIG &> /dev/null 77 | } 78 | 79 | # 80 | # Get config value by . 81 | # 82 | 83 | config_get() { 84 | local key=$1 85 | test -n "$key" \ 86 | && grep "^\[$ENV" -A 20 $CONFIG \ 87 | | grep "^$key" \ 88 | | head -n 1 \ 89 | | cut -d ' ' -f 2-999 90 | } 91 | 92 | # 93 | # Output version. 94 | # 95 | 96 | version() { 97 | echo $VERSION 98 | } 99 | 100 | # 101 | # Run the given remote . 102 | # 103 | 104 | run() { 105 | local url="`config_get user`@`config_get host`" 106 | local key=`config_get key` 107 | if test -n "$key"; then 108 | local shell="ssh -A -i $key $url" 109 | else 110 | local shell="ssh -A $url" 111 | fi 112 | echo $shell "\"$@\"" >> $LOG 113 | $shell $@ 114 | } 115 | 116 | # 117 | # Launch an interactive ssh console session. 118 | # 119 | 120 | console() { 121 | local url="`config_get user`@`config_get host`" 122 | local key=`config_get key` 123 | if test -n "$key"; then 124 | local shell="ssh -i $key $url" 125 | else 126 | local shell="ssh $url" 127 | fi 128 | echo $shell 129 | exec $shell 130 | } 131 | 132 | # 133 | # Output config or [key]. 134 | # 135 | 136 | config() { 137 | if test $# -eq 0; then 138 | cat $CONFIG 139 | else 140 | config_get $1 141 | fi 142 | } 143 | 144 | # 145 | # Execute hook relative to the path configured. 146 | # 147 | 148 | hook() { 149 | test -n "$1" || abort hook name required 150 | local hook=$1 151 | local path=`config_get path` 152 | local cmd=`config_get $hook` 153 | if test -n "$cmd"; then 154 | log "executing $hook \`$cmd\`" 155 | run "cd $path/current; \ 156 | SHARED=\"$path/shared\" \ 157 | $cmd 2>&1 | tee -a $LOG; \ 158 | exit \${PIPESTATUS[0]}" 159 | test $? -eq 0 160 | else 161 | log hook $hook 162 | fi 163 | } 164 | 165 | # 166 | # Run setup. 167 | # 168 | 169 | setup() { 170 | local path=`config_get path` 171 | local repo=`config_get repo` 172 | run "mkdir -p $path/{shared/{logs,pids},source}" 173 | test $? -eq 0 || abort setup paths failed 174 | log running setup 175 | log cloning $repo 176 | run "git clone $repo $path/source" 177 | test $? -eq 0 || abort failed to clone 178 | log setup complete 179 | } 180 | 181 | # 182 | # Deploy [ref]. 183 | # 184 | 185 | deploy() { 186 | local ref=$1 187 | local path=`config_get path` 188 | log deploying 189 | 190 | hook pre-deploy || abort pre-deploy hook failed 191 | 192 | # fetch source 193 | log fetching updates 194 | run "cd $path/source && git fetch --all" 195 | test $? -eq 0 || abort fetch failed 196 | 197 | # latest tag 198 | if test -z "$ref"; then 199 | log fetching latest tag 200 | ref=`run "cd $path/source && git for-each-ref refs/tags \ 201 | --sort=-authordate \ 202 | --format='%(refname)' \ 203 | --count=1 | cut -d '/' -f 3"` 204 | test $? -eq 0 || abort failed to determine latest tag 205 | fi 206 | 207 | # reset HEAD 208 | log resetting HEAD to $ref 209 | run "cd $path/source && git reset --hard $ref" 210 | test $? -eq 0 || abort git reset failed 211 | 212 | # link current 213 | run "ln -sfn $path/source $path/current" 214 | test $? -eq 0 || abort symlink failed 215 | 216 | # deploy log 217 | run "cd $path/source && \ 218 | echo \`git rev-parse --short HEAD\` \ 219 | >> $path/.deploys" 220 | test $? -eq 0 || abort deploy log append failed 221 | 222 | hook post-deploy || abort post-deploy hook failed 223 | 224 | if test $TEST -eq 1; then 225 | hook test 226 | if test $? -ne 0; then 227 | log tests failed, reverting deploy 228 | quickly_revert_to 1 && log "revert complete" && exit 229 | fi 230 | else 231 | log ignoring tests 232 | fi 233 | 234 | # done 235 | log successfully deployed $ref 236 | } 237 | 238 | # 239 | # Get current commit. 240 | # 241 | 242 | current_commit() { 243 | local path=`config_get path` 244 | run "cd $path/source && \ 245 | git rev-parse --short HEAD" 246 | } 247 | 248 | # 249 | # Get th deploy commit. 250 | # 251 | 252 | nth_deploy_commit() { 253 | local n=$1 254 | local path=`config_get path` 255 | run "cat $path/.deploys | tail -n $n | head -n 1 | cut -d ' ' -f 1" 256 | } 257 | 258 | # 259 | # List deploys. 260 | # 261 | 262 | list_deploys() { 263 | local path=`config_get path` 264 | run "cat $path/.deploys" 265 | } 266 | 267 | # 268 | # Revert to the th last deployment, ignoring tests. 269 | # 270 | 271 | quickly_revert_to() { 272 | local n=$1 273 | log "quickly reverting $n deploy(s)" 274 | local commit=`nth_deploy_commit $((n + 1))` 275 | TEST=0 deploy "$commit" 276 | } 277 | 278 | # 279 | # Revert to the th last deployment. 280 | # 281 | 282 | revert_to() { 283 | local n=$1 284 | log "reverting $n deploy(s)" 285 | local commit=`nth_deploy_commit $((n + 1))` 286 | deploy "$commit" 287 | } 288 | 289 | # 290 | # Require environment arg. 291 | # 292 | 293 | require_env() { 294 | config_section $ENV || abort "[$ENV] config section not defined" 295 | test -z "$ENV" && abort " required" 296 | } 297 | 298 | # 299 | # Update deploy. 300 | # 301 | 302 | update() { 303 | log "updating deploy(1)" 304 | rm -fr /tmp/deploy 305 | git clone git://github.com/visionmedia/deploy.git \ 306 | --depth 0 \ 307 | /tmp/deploy \ 308 | &> /tmp/deploy.log \ 309 | && cd /tmp/deploy \ 310 | && make install \ 311 | && log "updated $VERSION -> `./bin/deploy --version`" 312 | } 313 | 314 | # parse argv 315 | 316 | while test $# -ne 0; do 317 | arg=$1; shift 318 | case $arg in 319 | -h|--help) usage; exit ;; 320 | -V|--version) version; exit ;; 321 | -c|--config) set_config_path $1; shift ;; 322 | -C|--chdir) log cd $1; cd $1; shift ;; 323 | -T|--no-tests) TEST=0 ;; 324 | run|exec) require_env; run "cd `config_get path` && $@"; exit ;; 325 | console) require_env; console; exit ;; 326 | curr|current) require_env; current_commit; exit ;; 327 | prev|previous) require_env; nth_deploy_commit 2; exit ;; 328 | revert) require_env; revert_to ${1-1}; exit ;; 329 | setup) require_env; setup $@; exit ;; 330 | list) require_env; list_deploys; exit ;; 331 | update) update; exit ;; 332 | config) config $@; exit ;; 333 | *) 334 | if test -z "$ENV"; then 335 | ENV=$arg; 336 | else 337 | REF="$REF $arg"; 338 | fi 339 | ;; 340 | esac 341 | done 342 | 343 | require_env 344 | 345 | # deploy 346 | deploy "${REF:-`config_get ref`}" 347 | -------------------------------------------------------------------------------- /deploy.conf: -------------------------------------------------------------------------------- 1 | [production] 2 | user root 3 | host 96.126.109.170 4 | repo https://github.com/lelylan/mqtt 5 | ref origin/master 6 | path /home/deploy/mqtt 7 | post-deploy npm install && [ -e ../shared/pids/app.pid ] && sudo restart mqtt || sudo start mqtt 8 | -------------------------------------------------------------------------------- /lib/authorizer.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose') 2 | , debug = require('debug')('lelylan') 3 | , Device = require('../app/models/devices/device'); 4 | 5 | module.exports.authenticate = function(client, username, password, callback) { 6 | Device.findOne({ _id: username, secret: password }, function (err, doc) { 7 | doc ? debug('Authorized connection for device', doc.id) : debug('Error connecting', username, password); 8 | 9 | if (doc) client.device_id = doc.id 10 | callback(null, doc); 11 | }); 12 | } 13 | 14 | module.exports.authorizePublish = function(client, topic, payload, callback) { 15 | debug('AUTHORIZING SUBSCRIBE', client.device_id == topic.split('/')[1]); 16 | debug('PAYLOAD', payload.toString()); 17 | debug('TOPIC', topic); 18 | callback(null, client.device_id == topic.split('/')[1]); 19 | } 20 | 21 | module.exports.authorizeSubscribe = function(client, topic, callback) { 22 | debug('AUTHORIZING PUBLISH', client.device_id == topic.split('/')[1]); 23 | debug('TOPIC', topic); 24 | callback(null, client.device_id == topic.split('/')[1]); 25 | } 26 | -------------------------------------------------------------------------------- /lib/server.js: -------------------------------------------------------------------------------- 1 | var mosca = require('mosca') 2 | , authorizer = require('./authorizer') 3 | , debug = require('debug')('lelylan'); 4 | 5 | function app(settings) { 6 | 7 | var server = new mosca.Server(settings, done); 8 | function done() {} 9 | 10 | server.on('ready', setup); 11 | 12 | function setup() { 13 | server.authenticate = authorizer.authenticate; 14 | server.authorizePublish = authorizer.authorizePublish; 15 | server.authorizeSubscribe = authorizer.authorizeSubscribe; 16 | } 17 | 18 | return server 19 | } 20 | 21 | module.exports.start = app; 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lelylan.mqtt", 3 | "description": "Lelylan MQTT broket", 4 | "version": "0.1.0", 5 | "author": "Lelylan Dev Team ", 6 | "scripts": { 7 | "start": "node app.js", 8 | "test": "make bail" 9 | }, 10 | "dependencies": { 11 | "ascoltatori": "^0.11.5", 12 | "async": "^0.1.22", 13 | "bunyan": "^0.21.4", 14 | "debug": "^0.7.4", 15 | "foreman": "0.0.25", 16 | "mongodb": "^1.1.11", 17 | "mongoose": "^3.3.1", 18 | "mosca": "^0.26.2", 19 | "mqtt": "~0.3.1", 20 | "redis": "^0.12.1", 21 | "request": "^2.11.4" 22 | }, 23 | "engines": { 24 | "node": "~0.10.x", 25 | "npm": "~1.1.x" 26 | }, 27 | "devDependencies": { 28 | "mocha": "~1.11.0", 29 | "nock": "~0.13.7", 30 | "database-cleaner": "~0.7.0", 31 | "factory-lady": "~0.1.0", 32 | "supertest": "~0.7.0", 33 | "async": "~0.1.22", 34 | "chai": "~1.7.2", 35 | "sinon": "~1.7.3", 36 | "sinon-chai": "~2.4.0", 37 | "chai-fuzzy": "~1.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /test/app_spec.js: -------------------------------------------------------------------------------- 1 | var Factory = require('factory-lady') 2 | , Device = require('../app/models/devices/device'); 3 | 4 | var async = require('async') 5 | , server = require('../lib/server') 6 | , mqtt = require('mqtt') 7 | , ascoltatori = require('ascoltatori') 8 | , chai = require('chai') 9 | , expect = require('chai').expect 10 | , sinon = require('sinon') 11 | , debug = require('debug')('test'); 12 | 13 | chai.use(require('sinon-chai')); 14 | chai.use(require('chai-fuzzy')); 15 | 16 | require('./factories/devices/device'); 17 | 18 | var instance 19 | , device 20 | , ascoltatore = { 21 | type: 'mongo', 22 | uri: process.env.MONGOLAB_JOBS_HOST, 23 | db: process.env.MONGOLAB_JOBS_DB, 24 | pubsubCollection: 'mqtt', 25 | mongo: {} } 26 | , settings = { 27 | port: process.env.PORT || 11884, 28 | backend: ascoltatore } 29 | , opts = { 30 | keepalive: 1000, 31 | clientId: 'mosca_' + require('crypto').randomBytes(16).toString('hex'), 32 | protocolId: 'MQIsdp', 33 | protocolVersion: 3 }; 34 | 35 | var portCounter = 30042 36 | , nextPort = function() { 37 | settings.port = ++portCounter; 38 | }; 39 | 40 | 41 | describe('MQTT client',function() { 42 | 43 | beforeEach(function(done) { 44 | nextPort(); 45 | instance = new server.start(settings); 46 | instance.on('ready', done) 47 | }); 48 | 49 | beforeEach(function(done) { 50 | Factory.create('device', {}, function(doc) { 51 | device = doc; 52 | done(); 53 | }); 54 | }); 55 | 56 | afterEach(function(done) { 57 | var instances = [instance]; 58 | 59 | async.parallel(instances.map(function(i) { 60 | return function(cb) { 61 | i.close(cb); 62 | }; 63 | }), function() { 64 | done(); 65 | }); 66 | }); 67 | 68 | function buildClient(done, callback) { 69 | var client = mqtt.createConnection(settings.port, settings.host); 70 | 71 | client.once('error', done); 72 | client.on('connected', function() { callback(client) }); 73 | client.stream.once('close', function() { done() }); 74 | } 75 | 76 | function buildAndConnect(done, callback) { 77 | buildClient(done, function(client) { 78 | client.connect(opts); 79 | client.on('connack', function(packet) { callback(client) }); 80 | }); 81 | } 82 | 83 | describe('with valid client ID and secret', function() { 84 | 85 | beforeEach(function() { 86 | opts.username = device.id; 87 | opts.password = device.secret; 88 | }); 89 | 90 | it('connects', function(done) { 91 | buildClient(done, function(client) { 92 | client.connect(opts); 93 | 94 | client.on('connack', function(packet) { 95 | expect(packet.returnCode).to.eql(0); 96 | client.disconnect(); 97 | }); 98 | }); 99 | }); 100 | 101 | describe('when publishing', function() { 102 | 103 | it('publishes to the authorizes device topic', function(done) { 104 | 105 | buildAndConnect(done, function(client) { 106 | 107 | var messageId = Math.floor(65535 * Math.random()); 108 | 109 | client.on('puback', function(packet) { 110 | expect(packet).to.have.property('messageId', messageId); 111 | client.disconnect(); 112 | }); 113 | 114 | client.publish({ 115 | qos: 1, 116 | topic: 'devices/' + device.id, 117 | payload: JSON.stringify({ properties: [] }), 118 | messageId: messageId 119 | }); 120 | }); 121 | }); 122 | 123 | it('can not publish to a not authorized device topic', function(done) { 124 | 125 | buildAndConnect(done, function(client) { 126 | 127 | // it exists no negation of auth, it just disconnect the client 128 | client.publish({ 129 | qos: 1, 130 | topic: 'devices/not-valid', 131 | payload: JSON.stringify({ properties: [] }), 132 | messageId: 42 133 | }); 134 | }); 135 | }); 136 | }); 137 | 138 | describe('when subscribing', function() { 139 | 140 | it('subscribes to the authorized device', function(done) { 141 | 142 | buildAndConnect(done, function(client) { 143 | 144 | var subscriptions = [{ topic: 'devices/' + device.id, qos: 0 }]; 145 | 146 | client.on('suback', function(packet) { 147 | client.disconnect(); 148 | }); 149 | 150 | client.subscribe({ 151 | subscriptions: subscriptions, 152 | messageId: 42 153 | }); 154 | }); 155 | }); 156 | 157 | it('can not subscribe to a not authorized device', function(done) { 158 | 159 | buildAndConnect(done, function(client) { 160 | 161 | var subscriptions = [{ topic: 'devices/not-valid', qos: 0 }]; 162 | 163 | // it exists no negation of auth, it just disconnect the client 164 | client.subscribe({ 165 | subscriptions: subscriptions, 166 | messageId: 42 167 | }); 168 | }); 169 | }); 170 | }); 171 | }); 172 | 173 | 174 | describe('with a not valid client ID or secret', function() { 175 | 176 | beforeEach(function() { 177 | opts.username = 'not-valid'; 178 | opts.password = 'not-valid'; 179 | }); 180 | 181 | it('does not connect', function(done) { 182 | buildClient(done, function(client) { 183 | 184 | // it exists no negation of auth, it just disconnect the client 185 | client.connect(opts); 186 | }); 187 | }); 188 | }); 189 | }); 190 | -------------------------------------------------------------------------------- /test/factories/devices/device.js: -------------------------------------------------------------------------------- 1 | var Factory = require('factory-lady') 2 | , Device = require('../../../app/models/devices/device') 3 | 4 | Factory.define('device', Device, { 5 | secret: 'device-secret' 6 | }); 7 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --reporter dot 2 | --ui bdd 3 | --growl 4 | --colors 5 | 6 | --------------------------------------------------------------------------------