├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── .husky └── pre-commit ├── .prettierrc.json ├── Dockerfile ├── Jenkinsfile ├── README.md ├── config └── index.js ├── docker-compose.yml ├── lib ├── SubscriptionEntry.js ├── SubscriptionManager.js ├── TopicStructure.js ├── WebSocket.js ├── WebSocketFactory.js └── WebSocketManager.js ├── nginx ├── Dockerfile └── nginx.conf ├── package-lock.json ├── package.json ├── src ├── ApplicationLogger.js ├── BrokerProcessMonitoring.js ├── broker.js └── config.json ├── test ├── config │ └── index.js ├── integration │ ├── api │ │ ├── authentication.js │ │ ├── cluster.js │ │ ├── command.js │ │ ├── configuration.js │ │ ├── device.js │ │ ├── devicetype.js │ │ ├── network.js │ │ ├── notification.js │ │ ├── server.js │ │ ├── subscription.js │ │ ├── token.js │ │ └── user.js │ ├── broker.spec.js │ ├── config.json │ └── constants.json └── unit │ ├── lib │ ├── SubscriptionEntry.spec.js │ ├── SubscriptionManager.spec.js │ ├── TopicStructure.spec.js │ ├── WebSocket.spec.js │ ├── WebSocketFactory.spec.js │ └── WebSocketManager.spec.js │ └── util │ └── DeviceHiveUtils.spec.js └── util ├── DeviceHiveUtils.js └── constants.json /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | .idea/* 4 | .env 5 | Dockerfile 6 | docker-compose.yml 7 | *.md -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env": { 4 | "commonjs": true, 5 | "es2021": true, 6 | "mocha": true 7 | }, 8 | "extends": ["google", "prettier"], 9 | "parserOptions": { 10 | "ecmaVersion": "latest" 11 | }, 12 | "rules": { 13 | "no-console": "error" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | 106 | # Docusaurus cache and generated files 107 | .docusaurus 108 | 109 | # Serverless directories 110 | .serverless/ 111 | 112 | # FuseBox cache 113 | .fusebox/ 114 | 115 | # DynamoDB Local files 116 | .dynamodb/ 117 | 118 | # TernJS port file 119 | .tern-port 120 | 121 | # Stores VSCode versions used for testing VSCode extensions 122 | .vscode-test 123 | 124 | # yarn v2 125 | .yarn/cache 126 | .yarn/unplugged 127 | .yarn/build-state.yml 128 | .yarn/install-state.gz 129 | .pnp.* 130 | 131 | .idea 132 | .idea/* 133 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npm run format:check 5 | npm run lint:check 6 | npm run test 7 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "semi": true, 5 | "singleQuote": false 6 | } 7 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.5-alpine 2 | 3 | MAINTAINER devicehive 4 | 5 | LABEL org.label-schema.url="https://devicehive.com" \ 6 | org.label-schema.vendor="DeviceHive" \ 7 | org.label-schema.vcs-url="https://github.com/devicehive/devicehive-mqtt" \ 8 | org.label-schema.name="devicehive-mqtt" \ 9 | org.label-schema.version="development" 10 | 11 | ENV WORK_DIR=/usr/src/app/ 12 | RUN mkdir -p ${WORK_DIR} \ 13 | && cd ${WORK_DIR} 14 | 15 | WORKDIR ${WORK_DIR} 16 | 17 | COPY . ${WORK_DIR} 18 | 19 | RUN npm install \ 20 | && npm install pm2 -g \ 21 | && npm cache clean --force 22 | 23 | EXPOSE 1883 24 | CMD ["pm2-docker", "src/broker.js"] 25 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | properties([ 2 | buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '7', numToKeepStr: '7')) 3 | ]) 4 | 5 | def publish_branches = ["development", "master"] 6 | 7 | stage('Build and publish Docker image in CI repository') { 8 | node('docker') { 9 | checkout scm 10 | echo 'Building image ...' 11 | def mqtt = docker.build("devicehiveci/devicehive-mqtt:${BRANCH_NAME}", '--pull -f Dockerfile .') 12 | 13 | echo 'Pushing image to CI repository ...' 14 | docker.withRegistry('https://registry.hub.docker.com', 'devicehiveci_dockerhub'){ 15 | mqtt.push() 16 | } 17 | } 18 | } 19 | 20 | if (publish_branches.contains(env.BRANCH_NAME)) { 21 | stage('Publish image in main repository') { 22 | node('docker') { 23 | // Builds from 'master' branch will have 'latest' tag 24 | def IMAGE_TAG = (env.BRANCH_NAME == 'master') ? 'latest' : env.BRANCH_NAME 25 | 26 | docker.withRegistry('https://registry.hub.docker.com', 'devicehiveci_dockerhub'){ 27 | sh """ 28 | docker tag devicehiveci/devicehive-mqtt:${BRANCH_NAME} registry.hub.docker.com/devicehive/devicehive-mqtt:${IMAGE_TAG} 29 | docker push registry.hub.docker.com/devicehive/devicehive-mqtt:${IMAGE_TAG} 30 | """ 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [devicehive]: https://devicehive.com "DeviceHive framework" 2 | [mosca]: https://github.com/mcollina/mosca "Mosca" 3 | [pm2]: https://github.com/Unitech/pm2 "PM2" 4 | [debug]: https://github.com/visionmedia/debug "debug" 5 | [redis]: https://redis.io/ "Redis" 6 | [docker]: https://www.docker.com/ "Docker" 7 | [docker compose]: https://docs.docker.com/compose/ "Docker Compose" 8 | [git]: https://git-scm.com/ "Git" 9 | [nodejs]: https://nodejs.org "NodeJS" 10 | [websocket api]: https://docs.devicehive.com/docs/websocket-api-reference "WebSocket API" 11 | [mqtt]: https://github.com/mqttjs/MQTT.js "MQTT" 12 | 13 | # devicehive-mqtt 14 | 15 | Additional resource: 16 | 17 | [DeviceHive Official site](https://devicehive.com) 18 | [DeviceHive Official playground](https://playground.devicehive.com) 19 | 20 | # MQTT plugin for DeviceHive server 21 | 22 | The **devicehive-mqtt** broker is a MQTT transport layer between MQTT clients and [DeviceHive] server. 23 | The broker uses WebSocket sessions to communicate with [DeviceHive] Server and Redis server for persistence functionality. 24 | The **devicehive-mqtt** broker supports _QOS0_ and _QOS1_, _retain messages_ and _last will_. 25 | The **devicehive-mqtt** broker is powered by [Mosca] MQTT broker library. 26 | 27 | ## Prerequisites 28 | 29 | - [NodeJS] (>=8) 30 | - [Redis] 31 | - [Docker] 32 | - [Docker Compose] 33 | - [Git] 34 | - cloned repository ( `git clone https://github.com/devicehive/devicehive-mqtt.git` ) 35 | 36 | # Start Up 37 | 38 | The **devicehive-mqtt** broker can be launched directly via node, docker container or docker-compose. 39 | With last choice is pretty easy to scale the broker horizontally. 40 | Also you might to specify a set of configurations/environmental variables that are described in the next paragraph. 41 | 42 | # Configuration 43 | 44 | ## Broker 45 | 46 | [path-to-broker-project]/src/config.json 47 | 48 | - **_BROKER_PORT_** - port on wich broker will start (default: 1883) 49 | - **_BROKER_WS_PORT_** - MQTT over WebSocket port (default: 3000) 50 | - **_WS_SERVER_URL_** - path to Web Socket server (default: ws://localhost:8080/dh/websocket) 51 | - **_REDIS_SERVER_HOST_** - Redis storage host (default: localhost) 52 | - **_REDIS_SERVER_PORT_** - Redis storage port (default: 6379) 53 | - **_APP_LOG_LEVEL_** - application logger level (levels: debug, info, warn, error) 54 | - **_ENABLE_PM_** - enable process monitoring with [PM2] module 55 | 56 | Each configuration field can be overridden with corresponding environmental variable with "BROKER" prefix, for example: 57 | 58 | BROKER.BROKER_PORT=6000 59 | 60 | Prefix separator can be overridden by **_ENVSEPARATOR_** environmental variable. Example: 61 | 62 | ENVSEPARATOR=_ 63 | BROKER_BROKER_PORT=6000 64 | 65 | ## Broker modules logging 66 | 67 | Through the "DEBUG" ([debug]) environment variable you are able to specify next modules loggers: 68 | 69 | - **_subscriptionmanager_** - SubscriptionManager module logging; 70 | - **_websocketfactory_** - WebSocketFactory module logging; 71 | - **_websocketmanager_** - WebSocketManager module logging; 72 | 73 | Example: 74 | 75 | DEBUG=subscriptionmanager,websocketfactory,websocketmanager 76 | 77 | ## Run with Node 78 | 79 | In the folder of cloned **devicehive-mqtt** repo run next commands: 80 | 81 | Install all dependencies: 82 | 83 | npm install 84 | 85 | Start broker: 86 | 87 | node ./src/broker.js 88 | 89 | Also, it's pretty useful to enable process monitoring with [PM2] module (ENABLE_PM environmental variables) and 90 | start the broker via PM2. 91 | 92 | Firstly, install the [PM2] globally: 93 | 94 | npm install -g pm2 95 | 96 | Then run next command: 97 | 98 | pm2 start ./src/broker.js 99 | 100 | After that, you will be able to see monitoring information provided by [PM2] library. 101 | Type the next command: 102 | 103 | pm2 monit 104 | 105 | ## Run with Docker 106 | 107 | In the folder of cloned **devicehive-mqtt** repo run next commands: 108 | 109 | Build docker container by [Dockerfile](./Dockerfile) located in the root folder of cloned **devicehive-mqtt** repo: 110 | 111 | docker build -t . 112 | 113 | Run docker container: 114 | 115 | docker run -p --env-file 116 | 117 | Where: 118 | 119 | _path-to-env-file_ - path to file with mentioned environmental variables. 120 | Do not specify BROKER*PORT variable. In the container it should be 1883, as by default. 121 | \_external-port* - port that will be used to achieve the broker 122 | 123 | ## Run with Docker Compose 124 | 125 | To run **devicehive-mqtt** broker with Docker Compose there is a [docker-compose.yml](./docker-compose.yml) file. 126 | You may edit this file in case you want change environment variables or add broker instances. 127 | 128 | To run just type the next command: 129 | 130 | docker-compose up 131 | 132 | # Connecting and authentication 133 | 134 | The [DeviceHive] provides few ways for authentication: 135 | 136 | 1. User _login_ and password 137 | 2. User _access token_ 138 | 139 | While connecting to the MQTT broker you can specify the username and password fields. 140 | After that you will be able to work with both common MQTT resources and [DeviceHive] resources. 141 | 142 | Also, you can connect to the MQTT broker without credentials. However, you will not be able 143 | to work with [DeviceHive] resources. From this state you are able to authenticate yourself with 144 | user access token. 145 | 146 | # DeviceHive messaging structure projection on MQTT topic structure 147 | 148 | [DeviceHive] has next structure entities: 149 | 150 | - network 151 | - device type 152 | - device 153 | - message type 154 | - notification 155 | - command 156 | 157 | As far as the broker uses WebSocket to communicate with DeviceHive server 158 | you can use [WebSocket API] to build request data objects. 159 | 160 | To mark topic as a private the MQTT client should add it's own clientId to the and of the topic over `@` sign 161 | 162 | To make request the MQTT client should publish to the next topic: 163 | 164 | dh/request 165 | 166 | To receive responses of request the MQTT client should subscribe to the response topics with request action mentioned: 167 | 168 | dh/response/@ 169 | 170 | Where _requestAction_ ia a request action (**user/get**, **device/delete**, **token/refresh etc.**) 171 | Response topic should be always private (e.g. with client ID mentioned) 172 | 173 | The MQTT client is able to subscribe to the notification/command/command_update topic to receive notification/command/command_update push messages 174 | 175 | dh/notification////[@] 176 | 177 | dh/command////[@] 178 | 179 | dh/command_update////[@] 180 | 181 | Where: 182 | 183 | - networkID - id of the network 184 | - deviceTypeID - id of the device type 185 | - deviceID - id of the device 186 | - notificationName - notification name 187 | - commandName - command name 188 | 189 | The **devicehive-mqtt** broker supports common wildcards in the topic ( +, #) 190 | 191 | All topics that are starts with `dh` appraised by the broker as a DeviceHive topic. 192 | All other topics are appraised as an ordinary MQTT topic. 193 | 194 | # Basic usages 195 | 196 | In this small code snippets we are using [MQTT] as a client library. 197 | 198 | _**Connection with username and password:**_ 199 | 200 | ```javascript 201 | const mqtt = require("mqtt"); 202 | 203 | const client = mqtt.connect("mqtt://localhost:1883", { 204 | username: "", 205 | password: "", 206 | }); 207 | 208 | client.on("connect", () => { 209 | //connection handler 210 | }); 211 | 212 | client.on("error", () => { 213 | //error handler 214 | }); 215 | ``` 216 | 217 | _**Connection without credentials, authentication with user access token:**_ 218 | 219 | ```javascript 220 | const mqtt = require("mqtt"); 221 | 222 | const client = mqtt.connect("mqtt://localhost:1883"); 223 | 224 | client.on("message", function (topic, message) { 225 | const messageObject = JSON.parse(message.toString()); 226 | 227 | if (messageObject.requestId === "12345") { 228 | if (messageObject.status === "success") { 229 | //client authenticated 230 | } 231 | } 232 | }); 233 | 234 | client.on("connect", () => { 235 | client.subscribe("dh/response/authenticate@" + client.options.clientId); 236 | 237 | client.publish("dh/request", { 238 | action: "authenticate", 239 | token: "", 240 | requestId: "12345", 241 | }); 242 | }); 243 | 244 | client.on("error", () => { 245 | //error handler 246 | }); 247 | ``` 248 | 249 | _**Connection with username and password, subscription for notification and command push messages:**_ 250 | 251 | ```javascript 252 | const mqtt = require("mqtt"); 253 | 254 | const client = mqtt.connect("mqtt://localhost:1883", { 255 | username: "", 256 | password: "", 257 | }); 258 | 259 | client.on("message", function (topic, message) { 260 | const messageObject = JSON.parse(message.toString()); 261 | 262 | //notification/command push messages 263 | }); 264 | 265 | client.on("connect", () => { 266 | /* Subscribe for notification push messages with name = notificationName 267 | of device with id = deviceId on network with id = networkId */ 268 | client.subscribe( 269 | "dh/notification////" 270 | ); 271 | 272 | /* Subscribe for notification push messages with name = notificationName 273 | of any device on network with id = networkId */ 274 | client.subscribe( 275 | "dh/notification///+/" 276 | ); 277 | 278 | /* Subscribe for command push messages with name = commandName 279 | of device with id = deviceId on network with id = networkId */ 280 | client.subscribe( 281 | "dh/command////" 282 | ); 283 | 284 | /* Subscribe for command push messages on network with id = networkId 285 | for any device with any command name */ 286 | client.subscribe("dh/command//#"); 287 | }); 288 | 289 | client.on("error", () => { 290 | //error handler 291 | }); 292 | ``` 293 | 294 | _**Connection without credentials subscription and publishing to the ordinary topics:**_ 295 | 296 | ```javascript 297 | const mqtt = require("mqtt"); 298 | 299 | const client = mqtt.connect("mqtt://localhost:1883"); 300 | 301 | client.on("message", function (topic, message) { 302 | const messageObject = JSON.parse(message.toString()); 303 | 304 | // messages handler 305 | }); 306 | 307 | client.on("connect", () => { 308 | client.subscribe("ordinary/topic"); 309 | client.publish("another/ordinary/topic", { 310 | data: "someData", 311 | }); 312 | }); 313 | 314 | client.on("error", () => { 315 | //error handler 316 | }); 317 | ``` 318 | 319 | # Tests 320 | 321 | Unit tests: 322 | 323 | npm run unitTest 324 | 325 | Integration tests (broker should be ran on localhost:1883): 326 | 327 | npm run integrationTest 328 | -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`); 2 | const configurator = require(`json-evn-configurator`); 3 | 4 | module.exports = { 5 | broker: configurator(path.join(__dirname, `../src/config.json`), `BROKER`), 6 | }; 7 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | redis: 4 | image: "redis:alpine" 5 | container_name: redis 6 | ports: 7 | - "6379:6379" 8 | nginx: 9 | build: ./nginx 10 | container_name: nginx 11 | links: 12 | - broker1:broker1 13 | - broker2:broker2 14 | ports: 15 | - "1883:8883" 16 | broker1: 17 | build: . 18 | container_name: broker1 19 | links: 20 | - redis 21 | environment: 22 | - BROKER.WS_SERVER_URL=ws://playground.devicehive.com/api/websocket 23 | - BROKER.REDIS_SERVER_HOST=redis 24 | - BROKER.REDIS_SERVER_PORT=6379 25 | - BROKER.ENABLE_PM=false 26 | - BROKER.APP_LOG_LEVEL=debug 27 | - BROKER.BROKER_PORT=1883 28 | - DEBUG=subscriptionmanager,websocketfactory,websocketmanager 29 | broker2: 30 | build: . 31 | container_name: broker2 32 | links: 33 | - redis 34 | environment: 35 | - BROKER.WS_SERVER_URL=ws://playground.devicehive.com/api/websocket 36 | - BROKER.REDIS_SERVER_HOST=redis 37 | - BROKER.REDIS_SERVER_PORT=6379 38 | - BROKER.ENABLE_PM=false 39 | - BROKER.APP_LOG_LEVEL=debug 40 | - BROKER.BROKER_PORT=1883 41 | - DEBUG=subscriptionmanager,websocketfactory,websocketmanager 42 | -------------------------------------------------------------------------------- /lib/SubscriptionEntry.js: -------------------------------------------------------------------------------- 1 | /** 2 | * SubscriptionEntry. Used with SubscriptionManager to manage 3 | * subscription on the same topic from multiple users 4 | */ 5 | class SubscriptionEntry { 6 | /** 7 | * Create Subscription Entry 8 | * @param {string} subscriber 9 | * @param {string} subscriptionId 10 | */ 11 | constructor(subscriber, subscriptionId) { 12 | this.subscriptionIdMap = new Map(); 13 | 14 | this.subscriptionIdMap.set(subscriber, subscriptionId); 15 | this.subscriptionCounter = 1; 16 | } 17 | 18 | /** 19 | * Get subscription counter 20 | * @return {number} 21 | */ 22 | getSubscriptionCounter() { 23 | return this.subscriptionCounter; 24 | } 25 | 26 | /** 27 | * Add subscriber 28 | * @param {string} subscriber 29 | * @param {string} subscriptionId 30 | */ 31 | addSubscriber(subscriber, subscriptionId) { 32 | this.subscriptionIdMap.set(subscriber, subscriptionId); 33 | this.subscriptionCounter++; 34 | } 35 | 36 | /** 37 | * Remove subscriber 38 | * @param {string} subscriber 39 | */ 40 | removeSubscriber(subscriber) { 41 | this.subscriptionIdMap.delete(subscriber); 42 | this.subscriptionCounter--; 43 | } 44 | 45 | /** 46 | * Get subscriber by subscriptionId 47 | * @param {string} subscriptionId 48 | * @return {string} 49 | */ 50 | getSubscriber(subscriptionId) { 51 | let resultOwner = ``; 52 | 53 | this.subscriptionIdMap.forEach((subscriptionIdItem, ownerItem) => { 54 | if (subscriptionIdItem === subscriptionId) { 55 | resultOwner = ownerItem; 56 | } 57 | }); 58 | 59 | return resultOwner; 60 | } 61 | 62 | /** 63 | * Get all subscribers 64 | * @return {Array} 65 | */ 66 | getSubscribers() { 67 | const subscribers = []; 68 | 69 | this.subscriptionIdMap.forEach((subscriptionIdItem, ownerItem) => { 70 | subscribers.push(ownerItem); 71 | }); 72 | 73 | return subscribers; 74 | } 75 | 76 | /** 77 | * Get subscriptionId by subscriber 78 | * @param {string} subscriber 79 | * @return {string} 80 | */ 81 | getSubscriptionId(subscriber) { 82 | return this.subscriptionIdMap.get(subscriber); 83 | } 84 | 85 | /** 86 | * Check for subscriber 87 | * @param {string} subscriber 88 | * @return {boolean} 89 | */ 90 | hasSubscriber(subscriber) { 91 | return this.subscriptionIdMap.has(subscriber); 92 | } 93 | 94 | /** 95 | * check for subscriptionId 96 | * @param {string} subscriptionId 97 | * @return {boolean} 98 | */ 99 | hasSubscriptionId(subscriptionId) { 100 | let result = false; 101 | 102 | this.subscriptionIdMap.forEach((subscriptionIdItem) => { 103 | if (subscriptionId === subscriptionIdItem) { 104 | result = true; 105 | } 106 | }); 107 | 108 | return result; 109 | } 110 | } 111 | 112 | module.exports = SubscriptionEntry; 113 | -------------------------------------------------------------------------------- /lib/SubscriptionManager.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require(`events`); 2 | const debug = require(`debug`)(`subscriptionmanager`); 3 | const SubscriptionEntry = require(`./SubscriptionEntry.js`); 4 | 5 | /** 6 | * Subscription Manager class. Used to handle multiple subscription 7 | * @event message 8 | */ 9 | class SubscriptionManager extends EventEmitter { 10 | /** 11 | * Create Subscription Manager 12 | */ 13 | constructor() { 14 | super(); 15 | 16 | this.subscriptionAttemptsMap = new Map(); 17 | this.subscriptionEntryMap = new Map(); 18 | this.subscriptionExecutorMap = new Map(); 19 | this.subscriptionExecutCounterMap = new Map(); 20 | 21 | debug(`${this.constructor.name}`); 22 | } 23 | 24 | /** 25 | * Add subject subscriber 26 | * @param {string} subject 27 | * @param {string} subscriber 28 | * @param {string} subscriptionId 29 | */ 30 | addSubjectSubscriber(subject, subscriber, subscriptionId) { 31 | debug( 32 | `${this.addSubjectSubscriber.name}: ${subject} ${subscriber}, ${subscriptionId}` 33 | ); 34 | 35 | if (this.hasSubscription(subject)) { 36 | this.subscriptionEntryMap 37 | .get(subject) 38 | .addSubscriber(subscriber, subscriptionId); 39 | } else { 40 | this.subscriptionEntryMap.set( 41 | subject, 42 | new SubscriptionEntry(subscriber, subscriptionId) 43 | ); 44 | } 45 | } 46 | 47 | /** 48 | * Remove subject subscriber 49 | * @param {string} subject 50 | * @param {string} subscriber 51 | */ 52 | removeSubjectSubscriber(subject, subscriber) { 53 | debug(`${this.removeSubjectSubscriber.name}: ${subject} ${subscriber}`); 54 | 55 | if (this.hasSubscription(subject)) { 56 | const subscriptionEntry = this.subscriptionEntryMap.get(subject); 57 | 58 | subscriptionEntry.removeSubscriber(subscriber); 59 | 60 | if (subscriptionEntry.getSubscriptionCounter() === 0) { 61 | this.subscriptionEntryMap.delete(subject); 62 | this.subscriptionExecutorMap.delete(subject); 63 | this.subscriptionExecutCounterMap.delete(subject); 64 | this.subscriptionAttemptsMap.delete(subscriber); 65 | } 66 | } 67 | } 68 | 69 | /** 70 | * Check for subscription 71 | * @param {string} subject 72 | * @return {boolean} 73 | */ 74 | hasSubscription(subject) { 75 | return this.subscriptionEntryMap.has(subject); 76 | } 77 | 78 | /** 79 | * Get or create new subscription executor that will execute 80 | * the execFunc only once for all subject subscriptions 81 | * @param {string} subject 82 | * @param {Function} execFunc 83 | * @param {Array} rest 84 | * @return {Function} 85 | */ 86 | getSubscriptionExecutor(subject, execFunc, ...rest) { 87 | if (!this.subscriptionExecutorMap.has(subject)) { 88 | this.subscriptionExecutCounterMap.set(subject, 0); 89 | this.subscriptionExecutorMap.set(subject, () => { 90 | if (this.subscriptionEntryMap.has(subject)) { 91 | const subscriptionEntry = 92 | this.subscriptionEntryMap.get(subject); 93 | 94 | if (this.getExecutionCounter(subject) === 0) { 95 | execFunc.apply(this, rest); 96 | } 97 | 98 | this.incExecutionCounter(subject); 99 | 100 | if ( 101 | this.getExecutionCounter(subject) === 102 | subscriptionEntry.getSubscriptionCounter() 103 | ) { 104 | this.resetExecutionCounter(subject); 105 | this.subscriptionExecutorMap.delete(subject); 106 | } 107 | 108 | debug( 109 | `${ 110 | this.getSubscriptionExecutor.name 111 | }: ${subject} ${subscriptionEntry.getSubscriptionCounter()} ${this.getExecutionCounter( 112 | subject 113 | )}` 114 | ); 115 | } 116 | }); 117 | } 118 | 119 | return this.subscriptionExecutorMap.get(subject); 120 | } 121 | 122 | /** 123 | * Get all subject of the subscriber 124 | * @param {string} subscriber 125 | * @return {Array} 126 | */ 127 | getSubjects(subscriber) { 128 | const subjects = []; 129 | 130 | this.subscriptionEntryMap.forEach((subscriptionEntry, subject) => { 131 | if (subscriptionEntry.hasSubscriber(subscriber)) { 132 | subjects.push(subject); 133 | } 134 | }); 135 | 136 | return subjects; 137 | } 138 | 139 | /** 140 | * Get all subjects 141 | * @return {Array} 142 | */ 143 | getAllSubjects() { 144 | const subjects = []; 145 | 146 | this.subscriptionEntryMap.forEach((subscriptionEntry, subject) => { 147 | subjects.push(subject); 148 | }); 149 | 150 | return subjects; 151 | } 152 | 153 | /** 154 | * Find subject by subscriber and subscriptionId 155 | * @param {string} subscriber 156 | * @param {string} subscriptionId 157 | * @return {string} 158 | */ 159 | findSubject(subscriber, subscriptionId) { 160 | let resultSubject = ``; 161 | 162 | this.subscriptionEntryMap.forEach((subscriptionEntry, subject) => { 163 | if ( 164 | subscriptionEntry.hasSubscriber(subscriber) && 165 | subscriptionEntry.hasSubscriptionId(subscriptionId) 166 | ) { 167 | resultSubject = subject; 168 | } 169 | }); 170 | 171 | return resultSubject; 172 | } 173 | 174 | /** 175 | * Find subscriptionId by subscriber and subject 176 | * @param {string} subscriber 177 | * @param {string} subject 178 | * @return {string} 179 | */ 180 | findSubscriptionId(subscriber, subject) { 181 | let resultSubscriptionId = ``; 182 | 183 | if (this.subscriptionEntryMap.has(subject)) { 184 | resultSubscriptionId = this.subscriptionEntryMap 185 | .get(subject) 186 | .getSubscriptionId(subscriber); 187 | } 188 | 189 | return resultSubscriptionId; 190 | } 191 | 192 | /** 193 | * Get all subject subscribers 194 | * @param {string} subject 195 | * @return {Array} 196 | */ 197 | getSubscribers(subject) { 198 | return this.subscriptionEntryMap.get(subject).getSubscribers(); 199 | } 200 | 201 | /** 202 | * Get subject execution counter 203 | * @param {string} subject 204 | * @return {number} 205 | */ 206 | getExecutionCounter(subject) { 207 | return this.subscriptionExecutCounterMap.get(subject); 208 | } 209 | 210 | /** 211 | * Increment subject execution counter 212 | * @param {string} subject 213 | */ 214 | incExecutionCounter(subject) { 215 | const counter = this.subscriptionExecutCounterMap.get(subject); 216 | 217 | this.subscriptionExecutCounterMap.set(subject, counter + 1); 218 | } 219 | 220 | /** 221 | * Reset (set to 0) execution counter 222 | * @param {string} subject 223 | */ 224 | resetExecutionCounter(subject) { 225 | this.subscriptionExecutCounterMap.set(subject, 0); 226 | } 227 | 228 | /** 229 | * Add subscription attempt 230 | * @param {string} subscriber 231 | * @param {string} subject 232 | */ 233 | addSubscriptionAttempt(subscriber, subject) { 234 | debug(`${this.addSubscriptionAttempt.name}: ${subject} ${subscriber}`); 235 | 236 | if (this.subscriptionAttemptsMap.has(subscriber)) { 237 | this.subscriptionAttemptsMap.get(subscriber).add(subject); 238 | } else { 239 | this.subscriptionAttemptsMap.set( 240 | subscriber, 241 | new Set().add(subject) 242 | ); 243 | } 244 | } 245 | 246 | /** 247 | * Get all subscriber`s subscription attempts 248 | * @param {string} subscriber 249 | * @return {Array} 250 | */ 251 | getSubscriptionAttempts(subscriber) { 252 | let result = []; 253 | 254 | if (this.subscriptionAttemptsMap.has(subscriber)) { 255 | result = [...this.subscriptionAttemptsMap.get(subscriber)]; 256 | } 257 | 258 | return result; 259 | } 260 | 261 | /** 262 | * Remove subscription attempt 263 | * @param {string} subscriber 264 | * @param {string} subject 265 | */ 266 | removeSubscriptionAttempt(subscriber, subject) { 267 | debug( 268 | `${this.removeSubscriptionAttempt.name}: ${subject} ${subscriber}` 269 | ); 270 | 271 | if (this.subscriptionAttemptsMap.has(subscriber)) { 272 | this.subscriptionAttemptsMap.get(subscriber).delete(subject); 273 | } 274 | } 275 | 276 | /** 277 | * Check for existing subscription attempt 278 | * @param {string} subscriber 279 | * @param {string} subject 280 | * @return {boolean} 281 | */ 282 | hasSubscriptionAttempt(subscriber, subject) { 283 | let result = false; 284 | 285 | if (this.subscriptionAttemptsMap.has(subscriber)) { 286 | result = this.subscriptionAttemptsMap.get(subscriber).has(subject); 287 | } 288 | 289 | return result; 290 | } 291 | } 292 | 293 | module.exports = SubscriptionManager; 294 | -------------------------------------------------------------------------------- /lib/TopicStructure.js: -------------------------------------------------------------------------------- 1 | const CONST = require("../util/constants.json"); 2 | const LRU = require("lru-cache"); 3 | const cache = new LRU({ 4 | max: 10000, 5 | ttl: 1000 * 60 * 60, 6 | }); 7 | 8 | /** 9 | * TopicStructure class. Used to parse topic string 10 | */ 11 | class TopicStructure { 12 | /** 13 | * Create new topic structure 14 | * @param {string} topic 15 | */ 16 | constructor(topic = ``) { 17 | this.response = false; 18 | this.request = false; 19 | this.domain = ``; 20 | this.action = ``; 21 | this.network = ``; 22 | this.deviceType = ``; 23 | this.device = ``; 24 | this.name = ``; 25 | this.owner = ``; 26 | 27 | if (cache.has(topic)) { 28 | Object.assign(this, cache.get(topic)); 29 | } else { 30 | if (topic && topic.startsWith(`${CONST.TOPICS.PARTS.DH}/`)) { 31 | const [topicBody, owner] = topic.split( 32 | CONST.CLIENT_ID_TOPIC_SPLITTER 33 | ); 34 | const partedTopicBody = topicBody.split(`/`); 35 | 36 | this.owner = owner; 37 | this.response = 38 | partedTopicBody[1] === CONST.TOPICS.PARTS.RESPONSE; 39 | this.request = 40 | partedTopicBody[1] === CONST.TOPICS.PARTS.REQUEST; 41 | 42 | const shift = this.response || this.request ? 1 : 0; 43 | const network = partedTopicBody[2 + shift]; 44 | const deviceType = partedTopicBody[3 + shift]; 45 | const device = partedTopicBody[4 + shift]; 46 | let name = partedTopicBody[5 + shift]; 47 | 48 | name = 49 | this.hasOwner() && name 50 | ? name.split(CONST.CLIENT_ID_TOPIC_SPLITTER)[0] 51 | : name; 52 | 53 | this.domain = partedTopicBody[0]; 54 | this.action = partedTopicBody[1 + shift]; 55 | this.network = 56 | !network || CONST.MQTT.WILDCARDS.includes(network) 57 | ? `` 58 | : network; 59 | this.deviceType = 60 | !deviceType || CONST.MQTT.WILDCARDS.includes(deviceType) 61 | ? `` 62 | : deviceType; 63 | this.device = 64 | !device || CONST.MQTT.WILDCARDS.includes(device) 65 | ? `` 66 | : device; 67 | this.name = 68 | !name || CONST.MQTT.WILDCARDS.includes(name) ? `` : name; 69 | 70 | cache.set(topic, this); 71 | } 72 | } 73 | } 74 | 75 | /** 76 | * Get topic domain 77 | * @return {string} 78 | */ 79 | getDomain() { 80 | return this.domain; 81 | } 82 | 83 | /** 84 | * Check that topic has owner 85 | * @return {boolean} 86 | */ 87 | hasOwner() { 88 | return !!this.owner; 89 | } 90 | 91 | /** 92 | * Get topic owner 93 | * @return {string} 94 | */ 95 | getOwner() { 96 | return this.owner; 97 | } 98 | 99 | /** 100 | * Is subscription topic 101 | * @return {boolean} 102 | */ 103 | isSubscription() { 104 | return !this.response && !this.request; 105 | } 106 | 107 | /** 108 | * Is response topic 109 | * @return {boolean} 110 | */ 111 | isResponse() { 112 | return this.response; 113 | } 114 | 115 | /** 116 | * Is request topic 117 | * @return {boolean} 118 | */ 119 | isRequest() { 120 | return this.request; 121 | } 122 | 123 | /** 124 | * Get topic action 125 | * @return {string} 126 | */ 127 | getAction() { 128 | return this.action; 129 | } 130 | 131 | /** 132 | * Get topic network 133 | * @return {Array} 134 | */ 135 | getNetworkIds() { 136 | return !this.device && this.network ? [this.network] : undefined; 137 | } 138 | 139 | /** 140 | * Get topic device type 141 | * @return {Array} 142 | */ 143 | getDeviceTypeIds() { 144 | return !this.device && this.deviceType ? [this.deviceType] : undefined; 145 | } 146 | 147 | /** 148 | * Get topic device 149 | * @return {string} 150 | */ 151 | getDevice() { 152 | return this.device || undefined; 153 | } 154 | 155 | /** 156 | * Get topic Name 157 | * @return {Array} 158 | */ 159 | getNames() { 160 | return this.name ? [this.name] : undefined; 161 | } 162 | 163 | /** 164 | * Is DeviceHive topic 165 | * @return {boolean} 166 | */ 167 | isDH() { 168 | return this.domain === CONST.TOPICS.PARTS.DH; 169 | } 170 | 171 | /** 172 | * Is notification topic 173 | * @return {boolean} 174 | */ 175 | isNotification() { 176 | return this.action === CONST.TOPICS.PARTS.NOTIFICATION; 177 | } 178 | 179 | /** 180 | * Is command topic 181 | * @return {boolean} 182 | */ 183 | isCommandInsert() { 184 | return this.action === CONST.TOPICS.PARTS.COMMAND; 185 | } 186 | 187 | /** 188 | * Is command with update topic 189 | * @return {boolean} 190 | */ 191 | isCommandUpdate() { 192 | return this.action === CONST.TOPICS.PARTS.COMMAND_UPDATE; 193 | } 194 | 195 | /** 196 | * TODO rework 197 | * Convert data object to topic 198 | * @param {Object} dataObject 199 | * @param {string} owner 200 | * @return {string} 201 | */ 202 | static toTopicString(dataObject, owner = "") { 203 | let topicParts; 204 | 205 | if (dataObject.subscriptionId) { 206 | const action = 207 | dataObject.action === CONST.WS.ACTIONS.NOTIFICATION_INSERT 208 | ? CONST.TOPICS.PARTS.NOTIFICATION 209 | : dataObject.action === CONST.WS.ACTIONS.COMMAND_INSERT 210 | ? CONST.TOPICS.PARTS.COMMAND 211 | : CONST.TOPICS.PARTS.COMMAND_UPDATE; 212 | const propertyKey = 213 | dataObject.action === CONST.WS.ACTIONS.NOTIFICATION_INSERT 214 | ? CONST.TOPICS.PARTS.NOTIFICATION 215 | : CONST.TOPICS.PARTS.COMMAND; 216 | const network = dataObject[propertyKey].networkId; 217 | const deviceType = dataObject[propertyKey].deviceTypeId; 218 | const device = dataObject[propertyKey].deviceId; 219 | const name = dataObject[propertyKey][propertyKey]; 220 | 221 | topicParts = [ 222 | CONST.TOPICS.PARTS.DH, 223 | action, 224 | network, 225 | deviceType, 226 | device, 227 | name, 228 | ]; 229 | } else { 230 | topicParts = [ 231 | CONST.TOPICS.PARTS.DH, 232 | CONST.TOPICS.PARTS.RESPONSE, 233 | dataObject.action, 234 | ]; 235 | } 236 | 237 | return owner 238 | ? `${topicParts.join("/")}${CONST.CLIENT_ID_TOPIC_SPLITTER}${owner}` 239 | : topicParts.join("/"); 240 | } 241 | } 242 | 243 | module.exports = TopicStructure; 244 | -------------------------------------------------------------------------------- /lib/WebSocket.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require(`events`); 2 | const WS = require(`ws`); 3 | 4 | const MAX_LISTENERS = 20; 5 | 6 | /** 7 | * WebSocket class wrapper 8 | * @event open 9 | * @event close 10 | * @event message 11 | */ 12 | class WebSocket extends EventEmitter { 13 | /** 14 | * Create WebSocket 15 | * @param {string} serverURL 16 | */ 17 | constructor(serverURL) { 18 | super(); 19 | 20 | this.ws = new WS(serverURL); 21 | this.ws.setMaxListeners(MAX_LISTENERS); 22 | 23 | this.ws.addEventListener(`open`, () => { 24 | this.emit(`open`); 25 | }); 26 | 27 | this.ws.addEventListener(`close`, () => { 28 | this.emit(`close`); 29 | }); 30 | 31 | this.ws.addEventListener(`message`, (message) => { 32 | this.emit(`message`, message); 33 | }); 34 | } 35 | 36 | /** 37 | * Close WebSocket session 38 | */ 39 | close() { 40 | this.ws.close(); 41 | } 42 | 43 | /** 44 | * Add event listener for WebSocket 45 | * @param {string} eventName 46 | * @param {Function} listener 47 | */ 48 | addEventListener(eventName, listener) { 49 | this.ws.addEventListener(eventName, listener); 50 | } 51 | 52 | /** 53 | * Remove event listener for WebSocket 54 | * @param {string} eventName 55 | * @param {Function} listener 56 | */ 57 | removeEventListener(eventName, listener) { 58 | this.ws.removeListener(eventName, listener); 59 | } 60 | 61 | /** 62 | * Send data as string 63 | * @param {string} str 64 | * @param {Function} callback 65 | */ 66 | sendString(str, callback) { 67 | this.ws.send(str, callback); 68 | } 69 | } 70 | 71 | module.exports = WebSocket; 72 | -------------------------------------------------------------------------------- /lib/WebSocketFactory.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require("events"); 2 | const debug = require(`debug`)(`websocketfactory`); 3 | const WebSocket = require("./WebSocket.js"); 4 | 5 | /** 6 | * WebSocket Factory. For supporting multiple users 7 | * @event message 8 | */ 9 | class WebSocketFactory extends EventEmitter { 10 | /** 11 | * Create WebSocketFactory 12 | * @param {string} serverUrl 13 | */ 14 | constructor(serverUrl) { 15 | super(); 16 | 17 | this.socketMap = new Map(); 18 | this.url = serverUrl; 19 | 20 | debug(`${this.constructor.name}: ${serverUrl}`); 21 | } 22 | 23 | /** 24 | * Get WebSocket by key or create new WebSocket 25 | * @param {string} key 26 | * @return {Promise} 27 | */ 28 | getSocket(key) { 29 | if (this.socketMap.has(key)) { 30 | const socket = this.socketMap.get(key); 31 | 32 | debug(`${this.getSocket.name}: ${key}`); 33 | 34 | return Promise.resolve(socket); 35 | } else { 36 | const socket = new WebSocket(this.url); 37 | 38 | socket.addEventListener("message", (message) => 39 | this.emit("message", key, message) 40 | ); 41 | 42 | this.socketMap.set(key, socket); 43 | 44 | return new Promise((resolve) => 45 | socket.addEventListener("open", () => resolve(socket)) 46 | ); 47 | } 48 | } 49 | 50 | /** 51 | * Remove WebSocket by key 52 | * @param {string} key 53 | */ 54 | removeSocket(key) { 55 | debug(`${this.removeSocket.name}: ${key}`); 56 | 57 | if (this.socketMap.has(key)) { 58 | this.socketMap.get(key).close(); 59 | this.socketMap.delete(key); 60 | } 61 | } 62 | 63 | /** 64 | * Check for socket with such key 65 | * @param {string} key 66 | * @return {boolean} 67 | */ 68 | hasSocket(key) { 69 | return this.socketMap.has(key); 70 | } 71 | } 72 | 73 | module.exports = WebSocketFactory; 74 | -------------------------------------------------------------------------------- /lib/WebSocketManager.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../util/constants.json`); 2 | const debug = require(`debug`)(`websocketmanager`); 3 | const EventEmitter = require(`events`); 4 | const WebSocketFactory = require(`./WebSocketFactory.js`); 5 | const randomstring = require(`randomstring`); 6 | 7 | /** 8 | * WebSocket manager. Handles the multiple WS session routine. 9 | * Access and refresh tokens for each session. Authentication. 10 | */ 11 | class WebSocketManager extends EventEmitter { 12 | /** 13 | * Create Web Socket manager 14 | * @param {string} wsURL 15 | */ 16 | constructor(wsURL) { 17 | super(); 18 | 19 | this.keyTokensMap = new Map(); 20 | this.authorizedKeySet = new Set(); 21 | this.keyLockCounterMap = new Map(); 22 | this.wsFactory = new WebSocketFactory(wsURL); 23 | 24 | this.wsFactory.on(`message`, (key, message) => 25 | this.emit(`message`, key, message) 26 | ); 27 | 28 | debug(`${this.constructor.name}: ${wsURL}`); 29 | } 30 | 31 | /** 32 | * Check for WS session by the key 33 | * @param {string} key 34 | * @return {boolean} 35 | */ 36 | hasKey(key) { 37 | return this.wsFactory.hasSocket(key); 38 | } 39 | 40 | /** 41 | * Set Access and Refresh token for WS session by the key 42 | * @param {string} key 43 | * @param {string} accessToken 44 | * @param {string} refreshToken 45 | */ 46 | setTokens(key, accessToken, refreshToken) { 47 | if (this.wsFactory.hasSocket(key)) { 48 | if (this.keyTokensMap.has(key)) { 49 | const oldTokens = this.keyTokensMap.get(key); 50 | 51 | this.keyTokensMap.set(key, { 52 | accessToken: accessToken || oldTokens.accessToken, 53 | refreshToken: refreshToken || oldTokens.refreshToken, 54 | }); 55 | } else { 56 | this.keyTokensMap.set(key, { accessToken, refreshToken }); 57 | } 58 | } 59 | } 60 | 61 | /** 62 | * Get Access and Refresh tokens of WS session by the key 63 | * @param {string} key 64 | * @return {boolean} 65 | */ 66 | getTokens(key) { 67 | let tokens = false; 68 | 69 | if (this.wsFactory.hasSocket(key) && this.keyTokensMap.has(key)) { 70 | tokens = this.keyTokensMap.get(key); 71 | } 72 | 73 | return tokens; 74 | } 75 | 76 | /** 77 | * Check for authorized WS session by the key 78 | * @param {string} key 79 | * @return {boolean} 80 | */ 81 | isAuthorized(key) { 82 | return this.wsFactory.hasSocket(key) && this.authorizedKeySet.has(key); 83 | } 84 | 85 | /** 86 | * Mark WS session by the key as authorized 87 | * @param {string} key 88 | */ 89 | setAuthorized(key) { 90 | debug(`${this.setAuthorized.name}: ${key}`); 91 | 92 | if (this.wsFactory.hasSocket(key)) { 93 | this.authorizedKeySet.add(key); 94 | } 95 | } 96 | 97 | /** 98 | * Remove authorized mark from WS session by the key 99 | * @param {string} key 100 | */ 101 | removeAuthorized(key) { 102 | debug(`${this.removeAuthorized.name}: ${key}`); 103 | 104 | this.authorizedKeySet.delete(key); 105 | } 106 | 107 | /** 108 | * Get access and refresh token for by user credentials 109 | * @param {string} key - key 110 | * @param {string} login - user login 111 | * @param {string} password - user password 112 | * @return {Promise} 113 | */ 114 | createTokens(key, login, password) { 115 | debug(`${this.createTokens.name}: ${key}`); 116 | 117 | return this.send(key, { 118 | action: CONST.WS.ACTIONS.TOKEN, 119 | login: login, 120 | password: password, 121 | }).then((tokens) => { 122 | this.setTokens(key, tokens); 123 | 124 | return tokens; 125 | }); 126 | } 127 | 128 | /** 129 | * Authenticate user by accessToken 130 | * @param {string} key - key 131 | * @param {string} accessToken 132 | * @return {Promise} 133 | */ 134 | authenticate(key, accessToken) { 135 | debug(`${this.authenticate.name}: ${key}`); 136 | 137 | return this.send(key, { 138 | action: CONST.WS.ACTIONS.AUTHENTICATE, 139 | token: accessToken, 140 | }).then(() => { 141 | this.setAuthorized(key); 142 | }); 143 | } 144 | 145 | /** 146 | * Send message object over WS session by the key 147 | * @param {string} key 148 | * @param {Object} messageObject 149 | * @return {Promise} 150 | */ 151 | send(key, messageObject) { 152 | debug(`${this.send.name}: ${key}`); 153 | 154 | this._lock(key); 155 | 156 | return this.wsFactory 157 | .getSocket(key) 158 | .then((wSocket) => { 159 | return new Promise((resolve, reject) => { 160 | const hasRequestId = messageObject.requestId; 161 | 162 | if (!hasRequestId) { 163 | messageObject.requestId = randomstring.generate(); 164 | } 165 | 166 | wSocket.sendString(JSON.stringify(messageObject)); 167 | 168 | const listener = (event) => { 169 | const messageData = JSON.parse(event.data); 170 | 171 | if (messageData.action === messageObject.action) { 172 | if ( 173 | messageData.requestId === 174 | messageObject.requestId 175 | ) { 176 | if (!hasRequestId) { 177 | delete messageData.requestId; 178 | } 179 | 180 | wSocket.removeListener(`message`, listener); 181 | 182 | if ( 183 | messageData.status === 184 | CONST.WS.SUCCESS_STATUS 185 | ) { 186 | resolve(messageData); 187 | } else { 188 | reject(messageData.error); 189 | } 190 | } 191 | } 192 | }; 193 | 194 | wSocket.addEventListener(`message`, listener); 195 | }); 196 | }) 197 | .then((response) => { 198 | this._unlock(key); 199 | return response; 200 | }) 201 | .catch((err) => { 202 | this._unlock(key); 203 | throw err; 204 | }); 205 | } 206 | 207 | /** 208 | * Send string message (forward string message) over WS session by the key 209 | * @param {string} key 210 | * @param {string} stringMessage 211 | */ 212 | sendString(key, stringMessage) { 213 | debug(`${this.sendString.name}: ${key}`); 214 | 215 | this._lock(key); 216 | 217 | this.wsFactory.getSocket(key).then((wSocket) => { 218 | if (this.wsFactory.hasSocket(key)) { 219 | wSocket.sendString(stringMessage, () => this._unlock(key)); 220 | } 221 | }); 222 | } 223 | 224 | /** 225 | * Close WS session by the key 226 | * @param {string} key 227 | */ 228 | close(key) { 229 | debug(`${this.close.name}: ${key}`); 230 | 231 | if (this._isLocked(key)) { 232 | this.once(`_unlock_${key}`, () => 233 | process.nextTick(() => this.close(key)) 234 | ); 235 | } else { 236 | this.removeAuthorized(key); 237 | this.keyLockCounterMap.delete(key); 238 | this.wsFactory.removeSocket(key); 239 | } 240 | } 241 | 242 | /** 243 | * Lock WS session resource 244 | * @param {string} key 245 | */ 246 | lock(key) { 247 | this._lock(key); 248 | } 249 | 250 | /** 251 | * Unlock WS session resource 252 | * @param {string} key 253 | */ 254 | unlock(key) { 255 | this._unlock(key); 256 | } 257 | 258 | /** 259 | * Lock WS session resource 260 | * @param {string} key 261 | * @private 262 | */ 263 | _lock(key) { 264 | debug(`${this._lock.name}: ${key}`); 265 | 266 | if (this.keyLockCounterMap.has(key)) { 267 | let counter = this.keyLockCounterMap.get(key); 268 | 269 | this.keyLockCounterMap.set(key, ++counter); 270 | 271 | if (counter === 1) { 272 | this.emit(`_lock_${key}`); 273 | } 274 | } else { 275 | this.keyLockCounterMap.set(key, 1); 276 | } 277 | } 278 | 279 | /** 280 | * Unlock WS session resource 281 | * @param {string} key 282 | * @private 283 | */ 284 | _unlock(key) { 285 | debug(`${this._unlock.name}: ${key}`); 286 | 287 | if (this.keyLockCounterMap.has(key)) { 288 | let counter = this.keyLockCounterMap.get(key); 289 | 290 | if (counter !== 0) { 291 | this.keyLockCounterMap.set(key, --counter); 292 | 293 | if (counter === 0) { 294 | this.emit(`_unlock_${key}`); 295 | } 296 | } 297 | } 298 | } 299 | 300 | /** 301 | * Check for locked WS session resource 302 | * @param {string} key 303 | * @return {boolean} 304 | * @private 305 | */ 306 | _isLocked(key) { 307 | let result = false; 308 | 309 | if (this.keyLockCounterMap.has(key)) { 310 | result = this.keyLockCounterMap.get(key) !== 0; 311 | } 312 | 313 | return result; 314 | } 315 | } 316 | 317 | module.exports = WebSocketManager; 318 | -------------------------------------------------------------------------------- /nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | 3 | COPY nginx.conf /etc/nginx/nginx.conf -------------------------------------------------------------------------------- /nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | events { 2 | worker_connections 1024; 3 | } 4 | 5 | stream { 6 | upstream mqtt_cluster { 7 | server broker1:1883; 8 | server broker2:1883; 9 | zone tcp_mem 64k; 10 | } 11 | 12 | server { 13 | listen 8883; 14 | proxy_pass mqtt_cluster; 15 | proxy_connect_timeout 1s; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "devicehive-mqtt", 3 | "version": "2.0.0", 4 | "description": "MQTT plugin for DeviceHive server", 5 | "main": "src/broker.js", 6 | "scripts": { 7 | "test": "mocha test/unit/**/**.spec.js", 8 | "unitTest": "mocha test/unit/**/**.spec.js", 9 | "integrationTest": "mocha test/integration/**/**.spec.js", 10 | "format:check": "prettier --check .", 11 | "format:write": "prettier --write .", 12 | "lint:check": "eslint .", 13 | "lint:fix": "eslint --fix .", 14 | "prepare": "husky install" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/devicehive/devicehive-mqtt.git" 19 | }, 20 | "keywords": [ 21 | "DeviceHive", 22 | "MQTT", 23 | "API", 24 | "IoT" 25 | ], 26 | "author": "Ihor Trambovetskyi (igortram@gmail.com)", 27 | "license": "Apache-2.0", 28 | "bugs": { 29 | "url": "https://github.com/devicehive/devicehive-mqtt/issues" 30 | }, 31 | "homepage": "https://github.com/devicehive/devicehive-mqtt#readme", 32 | "dependencies": { 33 | "aedes": "^0.47.0", 34 | "aedes-persistence-redis": "^9.0.1", 35 | "aedes-stats": "^4.0.0", 36 | "debug": "^4.3.4", 37 | "json-evn-configurator": "^1.0.6", 38 | "lru-cache": "^7.13.1", 39 | "mqemitter-redis": "^5.0.0", 40 | "pm2": "^5.2.0", 41 | "pmx": "^1.6.7", 42 | "randomstring": "^1.2.2", 43 | "winston": "^3.8.1", 44 | "ws": "^8.8.1" 45 | }, 46 | "devDependencies": { 47 | "chai": "^4.3.6", 48 | "eslint": "^8.20.0", 49 | "eslint-config-google": "^0.14.0", 50 | "eslint-config-prettier": "^8.5.0", 51 | "husky": "^8.0.1", 52 | "mocha": "^10.0.0", 53 | "mqtt": "^4.3.7", 54 | "prettier": "^2.7.1", 55 | "sinon": "^14.0.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/ApplicationLogger.js: -------------------------------------------------------------------------------- 1 | const winston = require(`winston`); 2 | 3 | /** 4 | * Application logger facade class. 5 | */ 6 | class ApplicationLogger { 7 | /** 8 | * Create new ApplicationLogger 9 | * @param {string} loggingLevel 10 | */ 11 | constructor(loggingLevel) { 12 | this.logger = winston.createLogger({ 13 | level: loggingLevel, 14 | transports: [new winston.transports.Console()], 15 | }); 16 | } 17 | 18 | /** 19 | * Error log 20 | * @param {string} str 21 | */ 22 | err(str) { 23 | this.logger.error(str); 24 | } 25 | 26 | /** 27 | * Warning log 28 | * @param {string} str 29 | */ 30 | warn(str) { 31 | this.logger.warn(str); 32 | } 33 | 34 | /** 35 | * Information log 36 | * @param {string} str 37 | */ 38 | info(str) { 39 | this.logger.info(str); 40 | } 41 | 42 | /** 43 | * Debug log 44 | * @param {string} str 45 | */ 46 | debug(str) { 47 | this.logger.debug(str); 48 | } 49 | } 50 | 51 | module.exports = ApplicationLogger; 52 | -------------------------------------------------------------------------------- /src/BrokerProcessMonitoring.js: -------------------------------------------------------------------------------- 1 | const CONST = require("../util/constants.json"); 2 | const probe = require("pmx").probe(); 3 | 4 | /** 5 | * Class for monitoring broker process 6 | */ 7 | class BrokerProcessMonitoring { 8 | /** 9 | * Create new BrokerProcessMonitoring 10 | */ 11 | constructor() { 12 | this.uptime = 0; 13 | this.time = ""; 14 | this.clientsTotal = 0; 15 | this.clientsMaximum = 0; 16 | this.publishedCount = 0; 17 | this.heapCurrent = 0; 18 | this.heapMaximum = 0; 19 | this.cpuUsage = 0; 20 | this.cpuAvg1 = 0; 21 | this.cpuAvg5 = 0; 22 | this.cpuAvg15 = 0; 23 | 24 | this._addPmxProbeMetric(`uptime`, `Uptime`); 25 | this._addPmxProbeMetric(`time`, `Broker time`); 26 | this._addPmxProbeMetric(`clientsTotal`, `Total connections`); 27 | this._addPmxProbeMetric(`clientsMaximum`, `Maximum connections`); 28 | this._addPmxProbeMetric(`publishedCount`, `Publishing count`); 29 | this._addPmxProbeMetric(`heapCurrent`, `Current heap`); 30 | this._addPmxProbeMetric(`heapMaximum`, `Maximum heap`); 31 | this._addPmxProbeMetric(`cpuUsage`, `Current CPU usage`); 32 | this._addPmxProbeMetric(`cpuAvg1`, `Average CPU usage (1min)`); 33 | this._addPmxProbeMetric(`cpuAvg5`, `Average CPU usage (5min)`); 34 | this._addPmxProbeMetric(`cpuAvg15`, `Average CPU usage (15min)`); 35 | } 36 | 37 | /** 38 | * Add PMX metric 39 | * @param {string} metricName 40 | * @param {string} description 41 | * @private 42 | */ 43 | _addPmxProbeMetric(metricName, description) { 44 | this[`${metricName}Metric`] = probe.metric({ 45 | name: description, 46 | value: () => { 47 | return this[metricName]; 48 | }, 49 | }); 50 | } 51 | 52 | /** 53 | * Update process metric 54 | * @param {string} metricName 55 | * @param {string|number} value 56 | */ 57 | updateMetric(metricName, value) { 58 | switch (metricName) { 59 | case CONST.MQTT.BROKER_STATS_TOPICS.UPTIME: 60 | this.uptime = value; 61 | break; 62 | case CONST.MQTT.BROKER_STATS_TOPICS.TIME: 63 | this.time = value; 64 | break; 65 | case CONST.MQTT.BROKER_STATS_TOPICS.CLIENTS_TOTAL: 66 | this.clientsTotal = value; 67 | break; 68 | case CONST.MQTT.BROKER_STATS_TOPICS.CLIENTS_MAXIMUM: 69 | this.clientsMaximum = value; 70 | break; 71 | case CONST.MQTT.BROKER_STATS_TOPICS.PUBLISH_SENT: 72 | this.publishedCount = value; 73 | break; 74 | case CONST.MQTT.BROKER_STATS_TOPICS.MEMORY_HEAP_CURRENT: 75 | this.heapCurrent = value; 76 | break; 77 | case CONST.MQTT.BROKER_STATS_TOPICS.MEMORY_HEAP_MAXIMUM: 78 | this.heapMaximum = value; 79 | break; 80 | case CONST.MQTT.BROKER_STATS_TOPICS.CPU_USAGE: 81 | this.cpuUsage = value; 82 | break; 83 | case CONST.MQTT.BROKER_STATS_TOPICS.CPU_AVG_1: 84 | this.cpuAvg1 = value; 85 | break; 86 | case CONST.MQTT.BROKER_STATS_TOPICS.CPU_AVG_5: 87 | this.cpuAvg5 = value; 88 | break; 89 | case CONST.MQTT.BROKER_STATS_TOPICS.CPU_AVG_15: 90 | this.cpuAvg15 = value; 91 | break; 92 | } 93 | } 94 | } 95 | 96 | module.exports = BrokerProcessMonitoring; 97 | -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "BROKER_PORT": 1883, 3 | "WS_SERVER_URL": "ws://localhost:8080/api/websocket", 4 | "REDIS_SERVER_HOST": "localhost", 5 | "REDIS_SERVER_PORT": "6379", 6 | "APP_LOG_LEVEL": "info", 7 | "STATS_INTERVAL_MS": 5000, 8 | "ENABLE_PM": true 9 | } 10 | -------------------------------------------------------------------------------- /test/config/index.js: -------------------------------------------------------------------------------- 1 | const path = require(`path`); 2 | const configurator = require(`json-evn-configurator`); 3 | 4 | module.exports = { 5 | test: { 6 | integration: configurator( 7 | path.join(__dirname, `../integration/config.json`), 8 | `INTEGRATION_TEST` 9 | ), 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /test/integration/api/authentication.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `authenticate`; 12 | const AUTHENTICATE_OPERATION = SUBJECT; 13 | const TOKEN_OPERATION = `token`; 14 | const AUTHENTICATE_ACTION = AUTHENTICATE_OPERATION; 15 | const AUTHENTICATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${AUTHENTICATE_ACTION}`; 16 | const TOKEN_ACTION = TOKEN_OPERATION; 17 | const TOKEN_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${TOKEN_ACTION}`; 18 | let mqttClient; 19 | 20 | it(`should connect to MQTT broker`, () => { 21 | return new Promise((resolve) => { 22 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 23 | username: Config.TEST_LOGIN, 24 | password: Config.TEST_PASSWORD, 25 | }); 26 | 27 | mqttClient.on(`message`, (topic, message) => { 28 | const messageObject = JSON.parse(message.toString()); 29 | 30 | ee.emit(messageObject.requestId, messageObject); 31 | }); 32 | 33 | mqttClient.on("connect", () => { 34 | resolve(); 35 | }); 36 | }); 37 | }); 38 | 39 | it(`should subscribe for "${AUTHENTICATE_TOPIC}" topic`, () => { 40 | return new Promise((resolve, reject) => { 41 | mqttClient.subscribe( 42 | `${AUTHENTICATE_TOPIC}@${mqttClient.options.clientId}`, 43 | (err) => { 44 | if (err) { 45 | reject(err); 46 | } else { 47 | resolve(); 48 | } 49 | } 50 | ); 51 | }); 52 | }); 53 | 54 | it(`should subscribe for "${TOKEN_TOPIC}" topic`, () => { 55 | return new Promise((resolve, reject) => { 56 | mqttClient.subscribe( 57 | `${TOKEN_TOPIC}@${mqttClient.options.clientId}`, 58 | (err) => { 59 | if (err) { 60 | reject(err); 61 | } else { 62 | resolve(); 63 | } 64 | } 65 | ); 66 | }); 67 | }); 68 | 69 | it(`should authenticate by access token received from server`, () => { 70 | const requestId1 = randomString.generate(); 71 | const requestId2 = randomString.generate(); 72 | 73 | return new Promise((resolve) => { 74 | ee.once(requestId1, (message) => { 75 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 76 | 77 | resolve(); 78 | }); 79 | 80 | ee.once(requestId2, (message) => { 81 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 82 | expect(message).to.include.all.keys(`accessToken`, `refreshToken`); 83 | 84 | mqttClient.publish( 85 | CONST.DH_REQUEST_TOPIC, 86 | JSON.stringify({ 87 | action: AUTHENTICATE_ACTION, 88 | requestId: requestId2, 89 | token: message.accessToken, 90 | }) 91 | ); 92 | }); 93 | 94 | mqttClient.publish( 95 | CONST.DH_REQUEST_TOPIC, 96 | JSON.stringify({ 97 | action: TOKEN_ACTION, 98 | requestId: requestId1, 99 | login: Config.TEST_LOGIN, 100 | password: Config.TEST_PASSWORD, 101 | }) 102 | ); 103 | }); 104 | }); 105 | 106 | it(`should disconnect from MQTT broker`, () => { 107 | return new Promise((resolve) => { 108 | mqttClient.end(() => { 109 | resolve(); 110 | }); 111 | }); 112 | }); 113 | -------------------------------------------------------------------------------- /test/integration/api/cluster.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `cluster`; 12 | const INFO_OPERATION = `info`; 13 | const INFO_ACTION = `${SUBJECT}/${INFO_OPERATION}`; 14 | const INFO_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INFO_ACTION}`; 15 | let mqttClient; 16 | 17 | it(`should connect to MQTT broker`, () => { 18 | return new Promise((resolve) => { 19 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 20 | username: Config.TEST_LOGIN, 21 | password: Config.TEST_PASSWORD, 22 | }); 23 | 24 | mqttClient.on(`message`, (topic, message) => { 25 | const messageObject = JSON.parse(message.toString()); 26 | 27 | ee.emit(messageObject.requestId, messageObject); 28 | }); 29 | 30 | mqttClient.on("connect", () => { 31 | resolve(); 32 | }); 33 | }); 34 | }); 35 | 36 | it(`should subscribe for "${INFO_TOPIC}" topic`, () => { 37 | return new Promise((resolve, reject) => { 38 | mqttClient.subscribe( 39 | `${INFO_TOPIC}@${mqttClient.options.clientId}`, 40 | (err) => { 41 | if (err) { 42 | reject(err); 43 | } else { 44 | resolve(); 45 | } 46 | } 47 | ); 48 | }); 49 | }); 50 | 51 | it(`should get cluster information`, () => { 52 | const requestId = randomString.generate(); 53 | 54 | return new Promise((resolve) => { 55 | ee.once(requestId, (message) => { 56 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 57 | expect(message).to.include.all.keys(`clusterInfo`); 58 | 59 | resolve(); 60 | }); 61 | 62 | mqttClient.publish( 63 | CONST.DH_REQUEST_TOPIC, 64 | JSON.stringify({ 65 | action: INFO_ACTION, 66 | requestId: requestId, 67 | }) 68 | ); 69 | }); 70 | }); 71 | 72 | it(`should disconnect from MQTT broker`, () => { 73 | return new Promise((resolve) => { 74 | mqttClient.end(() => { 75 | resolve(); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/integration/api/command.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `command`; 12 | const GET_OPERATION = `get`; 13 | const LIST_OPERATION = `list`; 14 | const INSERT_OPERATION = `insert`; 15 | const UPDATE_OPERATION = `update`; 16 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 17 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 18 | const INSERT_ACTION = `${SUBJECT}/${INSERT_OPERATION}`; 19 | const UPDATE_ACTION = `${SUBJECT}/${UPDATE_OPERATION}`; 20 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 21 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 22 | const INSERT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INSERT_ACTION}`; 23 | const UPDATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UPDATE_ACTION}`; 24 | const TEST_COMMAND_NAME = randomString.generate(); 25 | const START_TEST_COMMAND_PARAMETERS = { parameter: `startParameter` }; 26 | const UPDATED_TEST_COMMAND_PARAMETERS = { parameter: `updatedParameter` }; 27 | const COMMAND_LIFETIME = 20; 28 | let mqttClient; 29 | let testCommandId; 30 | 31 | it(`should connect to MQTT broker`, () => { 32 | return new Promise((resolve) => { 33 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 34 | username: Config.TEST_LOGIN, 35 | password: Config.TEST_PASSWORD, 36 | }); 37 | 38 | mqttClient.on(`message`, (topic, message) => { 39 | const messageObject = JSON.parse(message.toString()); 40 | 41 | ee.emit(messageObject.requestId, messageObject); 42 | }); 43 | 44 | mqttClient.on("connect", () => { 45 | resolve(); 46 | }); 47 | }); 48 | }); 49 | 50 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 51 | return new Promise((resolve, reject) => { 52 | mqttClient.subscribe( 53 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 54 | (err) => { 55 | if (err) { 56 | reject(err); 57 | } else { 58 | resolve(); 59 | } 60 | } 61 | ); 62 | }); 63 | }); 64 | 65 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 66 | return new Promise((resolve, reject) => { 67 | mqttClient.subscribe( 68 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 69 | (err) => { 70 | if (err) { 71 | reject(err); 72 | } else { 73 | resolve(); 74 | } 75 | } 76 | ); 77 | }); 78 | }); 79 | 80 | it(`should subscribe for "${INSERT_TOPIC}" topic`, () => { 81 | return new Promise((resolve, reject) => { 82 | mqttClient.subscribe( 83 | `${INSERT_TOPIC}@${mqttClient.options.clientId}`, 84 | (err) => { 85 | if (err) { 86 | reject(err); 87 | } else { 88 | resolve(); 89 | } 90 | } 91 | ); 92 | }); 93 | }); 94 | 95 | it(`should subscribe for "${UPDATE_TOPIC}" topic`, () => { 96 | return new Promise((resolve, reject) => { 97 | mqttClient.subscribe( 98 | `${UPDATE_TOPIC}@${mqttClient.options.clientId}`, 99 | (err) => { 100 | if (err) { 101 | reject(err); 102 | } else { 103 | resolve(); 104 | } 105 | } 106 | ); 107 | }); 108 | }); 109 | 110 | it(`should create new command with name: "${TEST_COMMAND_NAME}" for device with id: "${Config.DEVICE_ID}"`, () => { 111 | const requestId = randomString.generate(); 112 | 113 | return new Promise((resolve) => { 114 | ee.once(requestId, (message) => { 115 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 116 | expect(message.command).to.be.an(`object`); 117 | 118 | testCommandId = message.command.id; 119 | 120 | resolve(); 121 | }); 122 | 123 | mqttClient.publish( 124 | CONST.DH_REQUEST_TOPIC, 125 | JSON.stringify({ 126 | action: INSERT_ACTION, 127 | requestId: requestId, 128 | deviceId: Config.DEVICE_ID, 129 | command: { 130 | command: TEST_COMMAND_NAME, 131 | parameters: START_TEST_COMMAND_PARAMETERS, 132 | lifetime: COMMAND_LIFETIME, 133 | }, 134 | }) 135 | ); 136 | }); 137 | }); 138 | 139 | it(`should query the command with name: "${TEST_COMMAND_NAME}" and parameters: "${JSON.stringify( 140 | START_TEST_COMMAND_PARAMETERS 141 | )}"`, () => { 142 | const requestId = randomString.generate(); 143 | 144 | return new Promise((resolve) => { 145 | ee.once(requestId, (message) => { 146 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 147 | expect(message.command).to.be.an(`object`); 148 | expect(message.command.id).to.equal(testCommandId); 149 | expect(message.command.command).to.equal(TEST_COMMAND_NAME); 150 | expect(message.command.deviceId).to.equal(Config.DEVICE_ID); 151 | expect(message.command.parameters).to.deep.equal( 152 | START_TEST_COMMAND_PARAMETERS 153 | ); 154 | 155 | resolve(); 156 | }); 157 | 158 | mqttClient.publish( 159 | CONST.DH_REQUEST_TOPIC, 160 | JSON.stringify({ 161 | action: GET_ACTION, 162 | requestId: requestId, 163 | deviceId: Config.DEVICE_ID, 164 | commandId: testCommandId, 165 | }) 166 | ); 167 | }); 168 | }); 169 | 170 | it(`should query the list of command for device with id: "${Config.DEVICE_ID}" with existing command with name: "${TEST_COMMAND_NAME}"`, () => { 171 | const requestId = randomString.generate(); 172 | 173 | return new Promise((resolve) => { 174 | ee.once(requestId, (message) => { 175 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 176 | expect(message.commands).to.be.an(`array`); 177 | expect( 178 | message.commands.map((commandObject) => commandObject.id) 179 | ).to.include.members([testCommandId]); 180 | expect( 181 | message.commands.map((commandObject) => commandObject.command) 182 | ).to.include.members([TEST_COMMAND_NAME]); 183 | 184 | resolve(); 185 | }); 186 | 187 | mqttClient.publish( 188 | CONST.DH_REQUEST_TOPIC, 189 | JSON.stringify({ 190 | action: LIST_ACTION, 191 | requestId: requestId, 192 | deviceId: Config.DEVICE_ID, 193 | take: 1000, 194 | }) 195 | ); 196 | }); 197 | }); 198 | 199 | it(`should update the command parameters: "${JSON.stringify( 200 | START_TEST_COMMAND_PARAMETERS 201 | )}" to "${JSON.stringify(UPDATED_TEST_COMMAND_PARAMETERS)}"`, () => { 202 | const requestId = randomString.generate(); 203 | 204 | return new Promise((resolve) => { 205 | ee.once(requestId, (message) => { 206 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 207 | 208 | resolve(); 209 | }); 210 | 211 | mqttClient.publish( 212 | CONST.DH_REQUEST_TOPIC, 213 | JSON.stringify({ 214 | action: UPDATE_ACTION, 215 | requestId: requestId, 216 | deviceId: Config.DEVICE_ID, 217 | commandId: testCommandId, 218 | command: { 219 | parameters: UPDATED_TEST_COMMAND_PARAMETERS, 220 | }, 221 | }) 222 | ); 223 | }); 224 | }); 225 | 226 | it(`should query the updated command where updated parameters are: "${JSON.stringify( 227 | UPDATED_TEST_COMMAND_PARAMETERS 228 | )}"`, () => { 229 | const requestId = randomString.generate(); 230 | 231 | return new Promise((resolve) => { 232 | ee.once(requestId, (message) => { 233 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 234 | expect(message.command).to.be.an(`object`); 235 | expect(message.command.id).to.equal(testCommandId); 236 | expect(message.command.command).to.equal(TEST_COMMAND_NAME); 237 | expect(message.command.deviceId).to.equal(Config.DEVICE_ID); 238 | 239 | resolve(); 240 | }); 241 | 242 | mqttClient.publish( 243 | CONST.DH_REQUEST_TOPIC, 244 | JSON.stringify({ 245 | action: GET_ACTION, 246 | requestId: requestId, 247 | deviceId: Config.DEVICE_ID, 248 | commandId: testCommandId, 249 | }) 250 | ); 251 | }); 252 | }); 253 | 254 | it(`should disconnect from MQTT broker`, () => { 255 | return new Promise((resolve) => { 256 | mqttClient.end(() => { 257 | resolve(); 258 | }); 259 | }); 260 | }); 261 | -------------------------------------------------------------------------------- /test/integration/api/configuration.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `configuration`; 12 | const GET_OPERATION = `get`; 13 | const PUT_OPERATION = `put`; 14 | const DELETE_OPERATION = `delete`; 15 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 16 | const PUT_ACTION = `${SUBJECT}/${PUT_OPERATION}`; 17 | const DELETE_ACTION = `${SUBJECT}/${DELETE_OPERATION}`; 18 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 19 | const PUT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${PUT_ACTION}`; 20 | const DELETE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${DELETE_ACTION}`; 21 | const TEST_CONFIGURATION_NAME = randomString.generate(); 22 | const START_TEST_CONFIGURATION_VALUE = randomString.generate(); 23 | const UPDATED_TEST_CONFIGURATION_VALUE = randomString.generate(); 24 | let mqttClient; 25 | 26 | it(`should connect to MQTT broker`, () => { 27 | return new Promise((resolve) => { 28 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 29 | username: Config.TEST_LOGIN, 30 | password: Config.TEST_PASSWORD, 31 | }); 32 | 33 | mqttClient.on(`message`, (topic, message) => { 34 | const messageObject = JSON.parse(message.toString()); 35 | 36 | ee.emit(messageObject.requestId, messageObject); 37 | }); 38 | 39 | mqttClient.on("connect", () => { 40 | resolve(); 41 | }); 42 | }); 43 | }); 44 | 45 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 46 | return new Promise((resolve, reject) => { 47 | mqttClient.subscribe( 48 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 49 | (err) => { 50 | if (err) { 51 | reject(err); 52 | } else { 53 | resolve(); 54 | } 55 | } 56 | ); 57 | }); 58 | }); 59 | 60 | it(`should subscribe for "${PUT_TOPIC}" topic`, () => { 61 | return new Promise((resolve, reject) => { 62 | mqttClient.subscribe( 63 | `${PUT_TOPIC}@${mqttClient.options.clientId}`, 64 | (err) => { 65 | if (err) { 66 | reject(err); 67 | } else { 68 | resolve(); 69 | } 70 | } 71 | ); 72 | }); 73 | }); 74 | 75 | it(`should subscribe for "${DELETE_TOPIC}" topic`, () => { 76 | return new Promise((resolve, reject) => { 77 | mqttClient.subscribe( 78 | `${DELETE_TOPIC}@${mqttClient.options.clientId}`, 79 | (err) => { 80 | if (err) { 81 | reject(err); 82 | } else { 83 | resolve(); 84 | } 85 | } 86 | ); 87 | }); 88 | }); 89 | 90 | it(`should put new configuration with name: "${TEST_CONFIGURATION_NAME}" and value: "${START_TEST_CONFIGURATION_VALUE}"`, () => { 91 | const requestId = randomString.generate(); 92 | 93 | return new Promise((resolve) => { 94 | ee.once(requestId, (message) => { 95 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 96 | expect(message.configuration).to.be.an(`object`); 97 | expect(message.configuration.name).to.equal( 98 | TEST_CONFIGURATION_NAME 99 | ); 100 | expect(message.configuration.value).to.equal( 101 | START_TEST_CONFIGURATION_VALUE 102 | ); 103 | 104 | resolve(); 105 | }); 106 | 107 | mqttClient.publish( 108 | CONST.DH_REQUEST_TOPIC, 109 | JSON.stringify({ 110 | action: PUT_ACTION, 111 | requestId: requestId, 112 | name: TEST_CONFIGURATION_NAME, 113 | value: START_TEST_CONFIGURATION_VALUE, 114 | }) 115 | ); 116 | }); 117 | }); 118 | 119 | it(`should query configuration with name: "${TEST_CONFIGURATION_NAME}"`, () => { 120 | const requestId = randomString.generate(); 121 | 122 | return new Promise((resolve) => { 123 | ee.once(requestId, (message) => { 124 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 125 | expect(message.configuration).to.be.an(`object`); 126 | expect(message.configuration.name).to.equal( 127 | TEST_CONFIGURATION_NAME 128 | ); 129 | expect(message.configuration.value).to.equal( 130 | START_TEST_CONFIGURATION_VALUE 131 | ); 132 | 133 | resolve(); 134 | }); 135 | 136 | mqttClient.publish( 137 | CONST.DH_REQUEST_TOPIC, 138 | JSON.stringify({ 139 | action: GET_ACTION, 140 | requestId: requestId, 141 | name: TEST_CONFIGURATION_NAME, 142 | }) 143 | ); 144 | }); 145 | }); 146 | 147 | it(`should update configuration with name: "${TEST_CONFIGURATION_NAME}" by new value: "${UPDATED_TEST_CONFIGURATION_VALUE}"`, () => { 148 | const requestId = randomString.generate(); 149 | 150 | return new Promise((resolve) => { 151 | ee.once(requestId, (message) => { 152 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 153 | expect(message.configuration).to.be.an(`object`); 154 | expect(message.configuration.name).to.equal( 155 | TEST_CONFIGURATION_NAME 156 | ); 157 | expect(message.configuration.value).to.equal( 158 | UPDATED_TEST_CONFIGURATION_VALUE 159 | ); 160 | 161 | resolve(); 162 | }); 163 | 164 | mqttClient.publish( 165 | CONST.DH_REQUEST_TOPIC, 166 | JSON.stringify({ 167 | action: PUT_ACTION, 168 | requestId: requestId, 169 | name: TEST_CONFIGURATION_NAME, 170 | value: UPDATED_TEST_CONFIGURATION_VALUE, 171 | }) 172 | ); 173 | }); 174 | }); 175 | 176 | it(`should query updated configuration with name: "${TEST_CONFIGURATION_NAME}" and value: "${UPDATED_TEST_CONFIGURATION_VALUE}"`, () => { 177 | const requestId = randomString.generate(); 178 | 179 | return new Promise((resolve) => { 180 | ee.once(requestId, (message) => { 181 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 182 | expect(message.configuration).to.be.an(`object`); 183 | expect(message.configuration.name).to.equal( 184 | TEST_CONFIGURATION_NAME 185 | ); 186 | expect(message.configuration.value).to.equal( 187 | UPDATED_TEST_CONFIGURATION_VALUE 188 | ); 189 | 190 | resolve(); 191 | }); 192 | 193 | mqttClient.publish( 194 | CONST.DH_REQUEST_TOPIC, 195 | JSON.stringify({ 196 | action: GET_ACTION, 197 | requestId: requestId, 198 | name: TEST_CONFIGURATION_NAME, 199 | }) 200 | ); 201 | }); 202 | }); 203 | 204 | it(`should delete configuration with name: "${TEST_CONFIGURATION_NAME}"`, () => { 205 | const requestId = randomString.generate(); 206 | 207 | return new Promise((resolve) => { 208 | ee.once(requestId, (message) => { 209 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 210 | 211 | resolve(); 212 | }); 213 | 214 | mqttClient.publish( 215 | CONST.DH_REQUEST_TOPIC, 216 | JSON.stringify({ 217 | action: DELETE_ACTION, 218 | requestId: requestId, 219 | name: TEST_CONFIGURATION_NAME, 220 | }) 221 | ); 222 | }); 223 | }); 224 | 225 | it(`should check that configuration with name: "${TEST_CONFIGURATION_NAME}" has been deleted`, () => { 226 | const requestId = randomString.generate(); 227 | 228 | return new Promise((resolve) => { 229 | ee.once(requestId, (message) => { 230 | expect(message.status).to.equal(CONST.ERROR_STATUS); 231 | 232 | resolve(); 233 | }); 234 | 235 | mqttClient.publish( 236 | CONST.DH_REQUEST_TOPIC, 237 | JSON.stringify({ 238 | action: GET_ACTION, 239 | requestId: requestId, 240 | }) 241 | ); 242 | }); 243 | }); 244 | 245 | it(`should disconnect from MQTT broker`, () => { 246 | return new Promise((resolve) => { 247 | mqttClient.end(() => { 248 | resolve(); 249 | }); 250 | }); 251 | }); 252 | -------------------------------------------------------------------------------- /test/integration/api/device.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `device`; 12 | const GET_OPERATION = `get`; 13 | const LIST_OPERATION = `list`; 14 | const SAVE_OPERATION = `save`; 15 | const DELETE_OPERATION = `delete`; 16 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 17 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 18 | const SAVE_ACTION = `${SUBJECT}/${SAVE_OPERATION}`; 19 | const DELETE_ACTION = `${SUBJECT}/${DELETE_OPERATION}`; 20 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 21 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 22 | const SAVE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${SAVE_ACTION}`; 23 | const DELETE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${DELETE_ACTION}`; 24 | const DEVICE_1_ID = randomString.generate(); 25 | const DEVICE_2_ID = randomString.generate(); 26 | const DEVICE_1_NAME = randomString.generate(); 27 | const DEVICE_2_NAME = randomString.generate(); 28 | const DEVICE_1_NAME_UPDATED = randomString.generate(); 29 | let mqttClient; 30 | 31 | it(`should connect to MQTT broker`, () => { 32 | return new Promise((resolve) => { 33 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 34 | username: Config.TEST_LOGIN, 35 | password: Config.TEST_PASSWORD, 36 | }); 37 | 38 | mqttClient.on(`message`, (topic, message) => { 39 | const messageObject = JSON.parse(message.toString()); 40 | 41 | ee.emit(messageObject.requestId, messageObject); 42 | }); 43 | 44 | mqttClient.on("connect", () => { 45 | resolve(); 46 | }); 47 | }); 48 | }); 49 | 50 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 51 | return new Promise((resolve, reject) => { 52 | mqttClient.subscribe( 53 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 54 | (err) => { 55 | if (err) { 56 | reject(err); 57 | } else { 58 | resolve(); 59 | } 60 | } 61 | ); 62 | }); 63 | }); 64 | 65 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 66 | return new Promise((resolve, reject) => { 67 | mqttClient.subscribe( 68 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 69 | (err) => { 70 | if (err) { 71 | reject(err); 72 | } else { 73 | resolve(); 74 | } 75 | } 76 | ); 77 | }); 78 | }); 79 | 80 | it(`should subscribe for "${SAVE_TOPIC}" topic`, () => { 81 | return new Promise((resolve, reject) => { 82 | mqttClient.subscribe( 83 | `${SAVE_TOPIC}@${mqttClient.options.clientId}`, 84 | (err) => { 85 | if (err) { 86 | reject(err); 87 | } else { 88 | resolve(); 89 | } 90 | } 91 | ); 92 | }); 93 | }); 94 | 95 | it(`should subscribe for "${DELETE_TOPIC}" topic`, () => { 96 | return new Promise((resolve, reject) => { 97 | mqttClient.subscribe( 98 | `${DELETE_TOPIC}@${mqttClient.options.clientId}`, 99 | (err) => { 100 | if (err) { 101 | reject(err); 102 | } else { 103 | resolve(); 104 | } 105 | } 106 | ); 107 | }); 108 | }); 109 | 110 | it(`should create new device (1) with ID: "${DEVICE_1_ID}" and name: "${DEVICE_1_NAME}"`, () => { 111 | const requestId = randomString.generate(); 112 | 113 | return new Promise((resolve) => { 114 | ee.once(requestId, (message) => { 115 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 116 | resolve(); 117 | }); 118 | 119 | mqttClient.publish( 120 | CONST.DH_REQUEST_TOPIC, 121 | JSON.stringify({ 122 | action: SAVE_ACTION, 123 | requestId: requestId, 124 | deviceId: DEVICE_1_ID, 125 | device: { 126 | name: DEVICE_1_NAME, 127 | data: {}, 128 | networkId: Config.NETWORK_ID, 129 | }, 130 | }) 131 | ); 132 | }); 133 | }); 134 | 135 | it(`should create new device (2) with ID: "${DEVICE_2_ID}" and name: "${DEVICE_2_NAME}"`, () => { 136 | const requestId = randomString.generate(); 137 | 138 | return new Promise((resolve) => { 139 | ee.once(requestId, (message) => { 140 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 141 | resolve(); 142 | }); 143 | 144 | mqttClient.publish( 145 | CONST.DH_REQUEST_TOPIC, 146 | JSON.stringify({ 147 | action: SAVE_ACTION, 148 | requestId: requestId, 149 | deviceId: DEVICE_2_ID, 150 | device: { 151 | name: DEVICE_2_NAME, 152 | data: {}, 153 | networkId: Config.NETWORK_ID, 154 | }, 155 | }) 156 | ); 157 | }); 158 | }); 159 | 160 | it(`should query the device (1) with ID: "${DEVICE_1_ID}" and name: "${DEVICE_1_NAME}"`, () => { 161 | const requestId = randomString.generate(); 162 | 163 | return new Promise((resolve) => { 164 | ee.once(requestId, (message) => { 165 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 166 | expect(message.device).to.be.an(`object`); 167 | expect(message.device.id).to.equal(DEVICE_1_ID); 168 | expect(message.device.name).to.equal(DEVICE_1_NAME); 169 | 170 | resolve(); 171 | }); 172 | 173 | mqttClient.publish( 174 | CONST.DH_REQUEST_TOPIC, 175 | JSON.stringify({ 176 | action: GET_ACTION, 177 | requestId: requestId, 178 | deviceId: DEVICE_1_ID, 179 | }) 180 | ); 181 | }); 182 | }); 183 | 184 | it(`should query the device (2) with ID: "${DEVICE_2_ID}" and name: "${DEVICE_2_NAME}"`, () => { 185 | const requestId = randomString.generate(); 186 | 187 | return new Promise((resolve) => { 188 | ee.once(requestId, (message) => { 189 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 190 | expect(message.device).to.be.an(`object`); 191 | expect(message.device.id).to.equal(DEVICE_2_ID); 192 | expect(message.device.name).to.equal(DEVICE_2_NAME); 193 | 194 | resolve(); 195 | }); 196 | 197 | mqttClient.publish( 198 | CONST.DH_REQUEST_TOPIC, 199 | JSON.stringify({ 200 | action: GET_ACTION, 201 | requestId: requestId, 202 | deviceId: DEVICE_2_ID, 203 | }) 204 | ); 205 | }); 206 | }); 207 | 208 | it(`should query the list of devices with existing device (1) and device (2)`, () => { 209 | const requestId = randomString.generate(); 210 | 211 | return new Promise((resolve) => { 212 | ee.once(requestId, (message) => { 213 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 214 | expect( 215 | message.devices.map((deviceObject) => deviceObject.id) 216 | ).to.include.members([DEVICE_1_ID, DEVICE_2_ID]); 217 | expect( 218 | message.devices.map((deviceObject) => deviceObject.name) 219 | ).to.include.members([DEVICE_1_NAME, DEVICE_2_NAME]); 220 | 221 | resolve(); 222 | }); 223 | 224 | mqttClient.publish( 225 | CONST.DH_REQUEST_TOPIC, 226 | JSON.stringify({ 227 | action: LIST_ACTION, 228 | requestId: requestId, 229 | networkId: Config.NETWORK_ID, 230 | }) 231 | ); 232 | }); 233 | }); 234 | 235 | it(`should update the device (1) name: "${DEVICE_1_NAME}" to "${DEVICE_1_NAME_UPDATED}"`, () => { 236 | const requestId = randomString.generate(); 237 | 238 | return new Promise((resolve) => { 239 | ee.once(requestId, (message) => { 240 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 241 | resolve(); 242 | }); 243 | 244 | mqttClient.publish( 245 | CONST.DH_REQUEST_TOPIC, 246 | JSON.stringify({ 247 | action: SAVE_ACTION, 248 | requestId: requestId, 249 | deviceId: DEVICE_1_ID, 250 | device: { 251 | name: DEVICE_1_NAME_UPDATED, 252 | data: {}, 253 | networkId: Config.NETWORK_ID, 254 | }, 255 | }) 256 | ); 257 | }); 258 | }); 259 | 260 | it(`should query the updated device (1) with ID: "${DEVICE_2_ID}" and updated name: "${DEVICE_1_NAME_UPDATED}"`, () => { 261 | const requestId = randomString.generate(); 262 | 263 | return new Promise((resolve) => { 264 | ee.once(requestId, (message) => { 265 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 266 | expect(message.device).to.be.an(`object`); 267 | expect(message.device.id).to.equal(DEVICE_1_ID); 268 | expect(message.device.name).to.equal(DEVICE_1_NAME_UPDATED); 269 | 270 | resolve(); 271 | }); 272 | 273 | mqttClient.publish( 274 | CONST.DH_REQUEST_TOPIC, 275 | JSON.stringify({ 276 | action: GET_ACTION, 277 | requestId: requestId, 278 | deviceId: DEVICE_1_ID, 279 | }) 280 | ); 281 | }); 282 | }); 283 | 284 | it(`should delete device (1) with ID: "${DEVICE_1_ID}"`, () => { 285 | const requestId = randomString.generate(); 286 | 287 | return new Promise((resolve) => { 288 | ee.once(requestId, (message) => { 289 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 290 | resolve(); 291 | }); 292 | 293 | mqttClient.publish( 294 | CONST.DH_REQUEST_TOPIC, 295 | JSON.stringify({ 296 | action: DELETE_ACTION, 297 | requestId: requestId, 298 | deviceId: DEVICE_1_ID, 299 | }) 300 | ); 301 | }); 302 | }); 303 | 304 | it(`should delete device (2) with ID: "${DEVICE_2_ID}"`, () => { 305 | const requestId = randomString.generate(); 306 | 307 | return new Promise((resolve) => { 308 | ee.once(requestId, (message) => { 309 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 310 | resolve(); 311 | }); 312 | 313 | mqttClient.publish( 314 | CONST.DH_REQUEST_TOPIC, 315 | JSON.stringify({ 316 | action: DELETE_ACTION, 317 | requestId: requestId, 318 | deviceId: DEVICE_2_ID, 319 | }) 320 | ); 321 | }); 322 | }); 323 | 324 | it(`should query the list of devices without device (1) and device (2)`, () => { 325 | const requestId = randomString.generate(); 326 | 327 | return new Promise((resolve) => { 328 | ee.once(requestId, (message) => { 329 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 330 | expect( 331 | message.devices.map((deviceObject) => deviceObject.id) 332 | ).to.not.include.members([DEVICE_1_ID, DEVICE_2_ID]); 333 | 334 | resolve(); 335 | }); 336 | 337 | mqttClient.publish( 338 | CONST.DH_REQUEST_TOPIC, 339 | JSON.stringify({ 340 | action: LIST_ACTION, 341 | requestId: requestId, 342 | networkId: Config.NETWORK_ID, 343 | }) 344 | ); 345 | }); 346 | }); 347 | 348 | it(`should disconnect from MQTT broker`, () => { 349 | return new Promise((resolve) => { 350 | mqttClient.end(() => { 351 | resolve(); 352 | }); 353 | }); 354 | }); 355 | -------------------------------------------------------------------------------- /test/integration/api/devicetype.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `devicetype`; 12 | const LIST_OPERATION = `list`; 13 | const COUNT_OPERATION = `count`; 14 | const GET_OPERATION = `get`; 15 | const INSERT_OPERATION = `insert`; 16 | const UPDATE_OPERATION = `update`; 17 | const DELETE_OPERATION = `delete`; 18 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 19 | const COUNT_ACTION = `${SUBJECT}/${COUNT_OPERATION}`; 20 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 21 | const INSERT_ACTION = `${SUBJECT}/${INSERT_OPERATION}`; 22 | const UPDATE_ACTION = `${SUBJECT}/${UPDATE_OPERATION}`; 23 | const DELETE_ACTION = `${SUBJECT}/${DELETE_OPERATION}`; 24 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 25 | const COUNT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${COUNT_ACTION}`; 26 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 27 | const INSERT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INSERT_ACTION}`; 28 | const UPDATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UPDATE_ACTION}`; 29 | const DELETE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${DELETE_ACTION}`; 30 | const TEST_DEVICE_TYPE_NAME = randomString.generate(); 31 | const TEST_DEVICE_TYPE_DESCRIPTION = randomString.generate(); 32 | const TEST_DEVICE_TYPE_NEW_DESCRIPTION = randomString.generate(); 33 | let deviceTypeCount = 0; 34 | let mqttClient; 35 | let customDeviceTypeId; 36 | 37 | it(`should connect to MQTT broker`, () => { 38 | return new Promise((resolve) => { 39 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 40 | username: Config.TEST_LOGIN, 41 | password: Config.TEST_PASSWORD, 42 | }); 43 | 44 | mqttClient.on(`message`, (topic, message) => { 45 | const messageObject = JSON.parse(message.toString()); 46 | 47 | ee.emit(messageObject.requestId, messageObject); 48 | }); 49 | 50 | mqttClient.on("connect", () => { 51 | resolve(); 52 | }); 53 | }); 54 | }); 55 | 56 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 57 | return new Promise((resolve, reject) => { 58 | return mqttClient.subscribe( 59 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 60 | (err) => { 61 | return err ? reject(err) : resolve(); 62 | } 63 | ); 64 | }); 65 | }); 66 | 67 | it(`should subscribe for "${COUNT_TOPIC}" topic`, () => { 68 | return new Promise((resolve, reject) => { 69 | return mqttClient.subscribe( 70 | `${COUNT_TOPIC}@${mqttClient.options.clientId}`, 71 | (err) => { 72 | return err ? reject(err) : resolve(); 73 | } 74 | ); 75 | }); 76 | }); 77 | 78 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 79 | return new Promise((resolve, reject) => { 80 | return mqttClient.subscribe( 81 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 82 | (err) => { 83 | return err ? reject(err) : resolve(); 84 | } 85 | ); 86 | }); 87 | }); 88 | 89 | it(`should subscribe for "${INSERT_TOPIC}" topic`, () => { 90 | return new Promise((resolve, reject) => { 91 | return mqttClient.subscribe( 92 | `${INSERT_TOPIC}@${mqttClient.options.clientId}`, 93 | (err) => { 94 | return err ? reject(err) : resolve(); 95 | } 96 | ); 97 | }); 98 | }); 99 | 100 | it(`should subscribe for "${UPDATE_TOPIC}" topic`, () => { 101 | return new Promise((resolve, reject) => { 102 | return mqttClient.subscribe( 103 | `${UPDATE_TOPIC}@${mqttClient.options.clientId}`, 104 | (err) => { 105 | return err ? reject(err) : resolve(); 106 | } 107 | ); 108 | }); 109 | }); 110 | 111 | it(`should subscribe for "${DELETE_TOPIC}" topic`, () => { 112 | return new Promise((resolve, reject) => { 113 | return mqttClient.subscribe( 114 | `${DELETE_TOPIC}@${mqttClient.options.clientId}`, 115 | (err) => { 116 | return err ? reject(err) : resolve(); 117 | } 118 | ); 119 | }); 120 | }); 121 | 122 | it(`should get count of existing device types`, () => { 123 | const requestId = randomString.generate(); 124 | 125 | return new Promise((resolve) => { 126 | ee.once(requestId, (message) => { 127 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 128 | expect(message.count).to.be.a(`number`); 129 | deviceTypeCount = message.count; 130 | resolve(); 131 | }); 132 | 133 | mqttClient.publish( 134 | CONST.DH_REQUEST_TOPIC, 135 | JSON.stringify({ 136 | action: COUNT_ACTION, 137 | requestId: requestId, 138 | }) 139 | ); 140 | }); 141 | }); 142 | 143 | it(`should create new device type with name: "${TEST_DEVICE_TYPE_NAME}" and description: "${TEST_DEVICE_TYPE_DESCRIPTION}"`, () => { 144 | const requestId = randomString.generate(); 145 | 146 | return new Promise((resolve) => { 147 | ee.once(requestId, (message) => { 148 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 149 | expect(message.deviceType.id).to.be.a(`number`); 150 | customDeviceTypeId = message.deviceType.id; 151 | resolve(); 152 | }); 153 | 154 | mqttClient.publish( 155 | CONST.DH_REQUEST_TOPIC, 156 | JSON.stringify({ 157 | action: INSERT_ACTION, 158 | requestId: requestId, 159 | deviceType: { 160 | name: TEST_DEVICE_TYPE_NAME, 161 | description: TEST_DEVICE_TYPE_DESCRIPTION, 162 | }, 163 | }) 164 | ); 165 | }); 166 | }); 167 | 168 | it(`should get new count of existing device types increased by 1`, () => { 169 | const requestId = randomString.generate(); 170 | 171 | return new Promise((resolve) => { 172 | ee.once(requestId, (message) => { 173 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 174 | expect(message.count).to.be.a(`number`); 175 | expect(message.count).to.equal(deviceTypeCount + 1); 176 | deviceTypeCount = message.count; 177 | resolve(); 178 | }); 179 | 180 | mqttClient.publish( 181 | CONST.DH_REQUEST_TOPIC, 182 | JSON.stringify({ 183 | action: COUNT_ACTION, 184 | requestId: requestId, 185 | }) 186 | ); 187 | }); 188 | }); 189 | 190 | it(`should query the device type with name: "${TEST_DEVICE_TYPE_NAME}" and description: "${TEST_DEVICE_TYPE_DESCRIPTION}"`, () => { 191 | const requestId = randomString.generate(); 192 | 193 | return new Promise((resolve) => { 194 | ee.once(requestId, (message) => { 195 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 196 | expect(message.deviceTypes).to.be.an(`array`); 197 | expect(message.deviceTypes[0].id).to.equal(customDeviceTypeId); 198 | expect(message.deviceTypes[0].name).to.equal(TEST_DEVICE_TYPE_NAME); 199 | expect(message.deviceTypes[0].description).to.equal( 200 | TEST_DEVICE_TYPE_DESCRIPTION 201 | ); 202 | resolve(); 203 | }); 204 | 205 | mqttClient.publish( 206 | CONST.DH_REQUEST_TOPIC, 207 | JSON.stringify({ 208 | action: LIST_ACTION, 209 | requestId: requestId, 210 | name: TEST_DEVICE_TYPE_NAME, 211 | }) 212 | ); 213 | }); 214 | }); 215 | 216 | it(`should update device type with name: "${TEST_DEVICE_TYPE_NAME}" to new description: "${TEST_DEVICE_TYPE_NEW_DESCRIPTION}"`, () => { 217 | const requestId1 = randomString.generate(); 218 | const requestId2 = randomString.generate(); 219 | 220 | return new Promise((resolve) => { 221 | ee.once(requestId1, (message) => { 222 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 223 | 224 | mqttClient.publish( 225 | CONST.DH_REQUEST_TOPIC, 226 | JSON.stringify({ 227 | action: GET_ACTION, 228 | requestId: requestId2, 229 | deviceTypeId: customDeviceTypeId, 230 | }) 231 | ); 232 | }); 233 | 234 | ee.once(requestId2, (message) => { 235 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 236 | expect(message.deviceType).to.be.an(`object`); 237 | expect(message.deviceType.id).to.equal(customDeviceTypeId); 238 | expect(message.deviceType.name).to.equal(TEST_DEVICE_TYPE_NAME); 239 | expect(message.deviceType.description).to.equal( 240 | TEST_DEVICE_TYPE_NEW_DESCRIPTION 241 | ); 242 | resolve(); 243 | }); 244 | 245 | mqttClient.publish( 246 | CONST.DH_REQUEST_TOPIC, 247 | JSON.stringify({ 248 | action: UPDATE_ACTION, 249 | requestId: requestId1, 250 | deviceTypeId: customDeviceTypeId, 251 | deviceType: { 252 | name: TEST_DEVICE_TYPE_NAME, 253 | description: TEST_DEVICE_TYPE_NEW_DESCRIPTION, 254 | }, 255 | }) 256 | ); 257 | }); 258 | }); 259 | 260 | it(`should delete the device type with name: "${TEST_DEVICE_TYPE_NAME}" and description: "${TEST_DEVICE_TYPE_NEW_DESCRIPTION}"`, () => { 261 | const requestId = randomString.generate(); 262 | 263 | return new Promise((resolve) => { 264 | ee.once(requestId, (message) => { 265 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 266 | resolve(); 267 | }); 268 | 269 | mqttClient.publish( 270 | CONST.DH_REQUEST_TOPIC, 271 | JSON.stringify({ 272 | action: GET_ACTION, 273 | requestId: requestId, 274 | deviceTypeId: customDeviceTypeId, 275 | }) 276 | ); 277 | }); 278 | }); 279 | 280 | it(`should disconnect from MQTT broker`, () => { 281 | return new Promise((resolve) => { 282 | mqttClient.end(() => { 283 | resolve(); 284 | }); 285 | }); 286 | }); 287 | -------------------------------------------------------------------------------- /test/integration/api/network.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `network`; 12 | const GET_OPERATION = `get`; 13 | const LIST_OPERATION = `list`; 14 | const INSERT_OPERATION = `insert`; 15 | const UPDATE_OPERATION = `update`; 16 | const DELETE_OPERATION = `delete`; 17 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 18 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 19 | const INSERT_ACTION = `${SUBJECT}/${INSERT_OPERATION}`; 20 | const UPDATE_ACTION = `${SUBJECT}/${UPDATE_OPERATION}`; 21 | const DELETE_ACTION = `${SUBJECT}/${DELETE_OPERATION}`; 22 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 23 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 24 | const INSERT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INSERT_ACTION}`; 25 | const UPDATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UPDATE_ACTION}`; 26 | const DELETE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${DELETE_ACTION}`; 27 | const TEST_NETWORK_NAME = randomString.generate(); 28 | const TEST_NETWORK_DESCRIPTION = randomString.generate(); 29 | const UPDATED_TEST_NETWORK_DESCRIPTION = randomString.generate(); 30 | let mqttClient; 31 | let testNetworkId; 32 | 33 | it(`should connect to MQTT broker`, () => { 34 | return new Promise((resolve) => { 35 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 36 | username: Config.TEST_LOGIN, 37 | password: Config.TEST_PASSWORD, 38 | }); 39 | 40 | mqttClient.on(`message`, (topic, message) => { 41 | const messageObject = JSON.parse(message.toString()); 42 | 43 | ee.emit(messageObject.requestId, messageObject); 44 | }); 45 | 46 | mqttClient.on("connect", () => { 47 | resolve(); 48 | }); 49 | }); 50 | }); 51 | 52 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 53 | return new Promise((resolve, reject) => { 54 | mqttClient.subscribe( 55 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 56 | (err) => { 57 | if (err) { 58 | reject(err); 59 | } else { 60 | resolve(); 61 | } 62 | } 63 | ); 64 | }); 65 | }); 66 | 67 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 68 | return new Promise((resolve, reject) => { 69 | mqttClient.subscribe( 70 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 71 | (err) => { 72 | if (err) { 73 | reject(err); 74 | } else { 75 | resolve(); 76 | } 77 | } 78 | ); 79 | }); 80 | }); 81 | 82 | it(`should subscribe for "${INSERT_TOPIC}" topic`, () => { 83 | return new Promise((resolve, reject) => { 84 | mqttClient.subscribe( 85 | `${INSERT_TOPIC}@${mqttClient.options.clientId}`, 86 | (err) => { 87 | if (err) { 88 | reject(err); 89 | } else { 90 | resolve(); 91 | } 92 | } 93 | ); 94 | }); 95 | }); 96 | 97 | it(`should subscribe for "${UPDATE_TOPIC}" topic`, () => { 98 | return new Promise((resolve, reject) => { 99 | mqttClient.subscribe( 100 | `${UPDATE_TOPIC}@${mqttClient.options.clientId}`, 101 | (err) => { 102 | if (err) { 103 | reject(err); 104 | } else { 105 | resolve(); 106 | } 107 | } 108 | ); 109 | }); 110 | }); 111 | 112 | it(`should subscribe for "${DELETE_TOPIC}" topic`, () => { 113 | return new Promise((resolve, reject) => { 114 | mqttClient.subscribe( 115 | `${DELETE_TOPIC}@${mqttClient.options.clientId}`, 116 | (err) => { 117 | if (err) { 118 | reject(err); 119 | } else { 120 | resolve(); 121 | } 122 | } 123 | ); 124 | }); 125 | }); 126 | 127 | it(`should create new network with name: "${TEST_NETWORK_NAME}" and description: "${TEST_NETWORK_DESCRIPTION}"`, () => { 128 | const requestId = randomString.generate(); 129 | 130 | return new Promise((resolve) => { 131 | ee.once(requestId, (message) => { 132 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 133 | expect(message.network).to.be.an(`object`); 134 | 135 | testNetworkId = message.network.id; 136 | 137 | resolve(); 138 | }); 139 | 140 | mqttClient.publish( 141 | CONST.DH_REQUEST_TOPIC, 142 | JSON.stringify({ 143 | action: INSERT_ACTION, 144 | requestId: requestId, 145 | network: { 146 | name: TEST_NETWORK_NAME, 147 | description: TEST_NETWORK_DESCRIPTION, 148 | }, 149 | }) 150 | ); 151 | }); 152 | }); 153 | 154 | it(`should query the network name: "${TEST_NETWORK_NAME} and description: "${TEST_NETWORK_DESCRIPTION}"`, () => { 155 | const requestId = randomString.generate(); 156 | 157 | return new Promise((resolve) => { 158 | ee.once(requestId, (message) => { 159 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 160 | expect(message.network).to.be.an(`object`); 161 | expect(message.network.id).to.equal(testNetworkId); 162 | expect(message.network.name).to.equal(TEST_NETWORK_NAME); 163 | expect(message.network.description).to.equal( 164 | TEST_NETWORK_DESCRIPTION 165 | ); 166 | 167 | resolve(); 168 | }); 169 | 170 | mqttClient.publish( 171 | CONST.DH_REQUEST_TOPIC, 172 | JSON.stringify({ 173 | action: GET_ACTION, 174 | requestId: requestId, 175 | networkId: testNetworkId, 176 | }) 177 | ); 178 | }); 179 | }); 180 | 181 | it(`should query the list of networks with existing network name: "${TEST_NETWORK_NAME} and description: "${TEST_NETWORK_DESCRIPTION}"`, () => { 182 | const requestId = randomString.generate(); 183 | 184 | return new Promise((resolve) => { 185 | ee.once(requestId, (message) => { 186 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 187 | expect(message.networks).to.be.an(`array`); 188 | expect( 189 | message.networks.map((networkObject) => networkObject.id) 190 | ).to.include.members([testNetworkId]); 191 | expect( 192 | message.networks.map((networkObject) => networkObject.name) 193 | ).to.include.members([TEST_NETWORK_NAME]); 194 | expect( 195 | message.networks.map( 196 | (networkObject) => networkObject.description 197 | ) 198 | ).to.include.members([TEST_NETWORK_DESCRIPTION]); 199 | 200 | resolve(); 201 | }); 202 | 203 | mqttClient.publish( 204 | CONST.DH_REQUEST_TOPIC, 205 | JSON.stringify({ 206 | action: LIST_ACTION, 207 | requestId: requestId, 208 | take: 10, 209 | }) 210 | ); 211 | }); 212 | }); 213 | 214 | it(`should update the network description: "${TEST_NETWORK_DESCRIPTION}" to "${UPDATED_TEST_NETWORK_DESCRIPTION}"`, () => { 215 | const requestId = randomString.generate(); 216 | 217 | return new Promise((resolve) => { 218 | ee.once(requestId, (message) => { 219 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 220 | 221 | resolve(); 222 | }); 223 | 224 | mqttClient.publish( 225 | CONST.DH_REQUEST_TOPIC, 226 | JSON.stringify({ 227 | action: UPDATE_ACTION, 228 | requestId: requestId, 229 | networkId: testNetworkId, 230 | network: { 231 | description: UPDATED_TEST_NETWORK_DESCRIPTION, 232 | }, 233 | }) 234 | ); 235 | }); 236 | }); 237 | 238 | it(`should query the updated network where updated description is: "${UPDATED_TEST_NETWORK_DESCRIPTION}"`, () => { 239 | const requestId = randomString.generate(); 240 | 241 | return new Promise((resolve) => { 242 | ee.once(requestId, (message) => { 243 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 244 | expect(message.network).to.be.an(`object`); 245 | expect(message.network.id).to.equal(testNetworkId); 246 | expect(message.network.name).to.equal(TEST_NETWORK_NAME); 247 | expect(message.network.description).to.equal( 248 | UPDATED_TEST_NETWORK_DESCRIPTION 249 | ); 250 | 251 | resolve(); 252 | }); 253 | 254 | mqttClient.publish( 255 | CONST.DH_REQUEST_TOPIC, 256 | JSON.stringify({ 257 | action: GET_ACTION, 258 | requestId: requestId, 259 | networkId: testNetworkId, 260 | }) 261 | ); 262 | }); 263 | }); 264 | 265 | it(`should delete the network"`, () => { 266 | const requestId = randomString.generate(); 267 | 268 | return new Promise((resolve) => { 269 | ee.once(requestId, (message) => { 270 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 271 | 272 | resolve(); 273 | }); 274 | 275 | mqttClient.publish( 276 | CONST.DH_REQUEST_TOPIC, 277 | JSON.stringify({ 278 | action: DELETE_ACTION, 279 | requestId: requestId, 280 | networkId: testNetworkId, 281 | }) 282 | ); 283 | }); 284 | }); 285 | 286 | it(`should query the list of the networks without deleted network`, () => { 287 | const requestId = randomString.generate(); 288 | 289 | return new Promise((resolve) => { 290 | ee.once(requestId, (message) => { 291 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 292 | expect(message.networks).to.be.an(`array`); 293 | expect( 294 | message.networks.map((networkObject) => networkObject.id) 295 | ).to.not.include.members([testNetworkId]); 296 | 297 | resolve(); 298 | }); 299 | 300 | mqttClient.publish( 301 | CONST.DH_REQUEST_TOPIC, 302 | JSON.stringify({ 303 | action: LIST_ACTION, 304 | requestId: requestId, 305 | networkId: Config.NETWORK_ID, 306 | }) 307 | ); 308 | }); 309 | }); 310 | 311 | it(`should disconnect from MQTT broker`, () => { 312 | return new Promise((resolve) => { 313 | mqttClient.end(() => { 314 | resolve(); 315 | }); 316 | }); 317 | }); 318 | -------------------------------------------------------------------------------- /test/integration/api/notification.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `notification`; 12 | const GET_OPERATION = `get`; 13 | const LIST_OPERATION = `list`; 14 | const INSERT_OPERATION = `insert`; 15 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 16 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 17 | const INSERT_ACTION = `${SUBJECT}/${INSERT_OPERATION}`; 18 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 19 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 20 | const INSERT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INSERT_ACTION}`; 21 | const TEST_NOTIFICATION_NAME = randomString.generate(); 22 | const TEST_NOTIFICATION_PARAMETERS = { parameter: `startParameter` }; 23 | let mqttClient; 24 | let testNotificationId; 25 | 26 | it(`should connect to MQTT broker`, () => { 27 | return new Promise((resolve) => { 28 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 29 | username: Config.TEST_LOGIN, 30 | password: Config.TEST_PASSWORD, 31 | }); 32 | 33 | mqttClient.on(`message`, (topic, message) => { 34 | const messageObject = JSON.parse(message.toString()); 35 | 36 | ee.emit(messageObject.requestId, messageObject); 37 | }); 38 | 39 | mqttClient.on("connect", () => { 40 | resolve(); 41 | }); 42 | }); 43 | }); 44 | 45 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 46 | return new Promise((resolve, reject) => { 47 | mqttClient.subscribe( 48 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 49 | (err) => { 50 | if (err) { 51 | reject(err); 52 | } else { 53 | resolve(); 54 | } 55 | } 56 | ); 57 | }); 58 | }); 59 | 60 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 61 | return new Promise((resolve, reject) => { 62 | mqttClient.subscribe( 63 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 64 | (err) => { 65 | if (err) { 66 | reject(err); 67 | } else { 68 | resolve(); 69 | } 70 | } 71 | ); 72 | }); 73 | }); 74 | 75 | it(`should subscribe for "${INSERT_TOPIC}" topic`, () => { 76 | return new Promise((resolve, reject) => { 77 | mqttClient.subscribe( 78 | `${INSERT_TOPIC}@${mqttClient.options.clientId}`, 79 | (err) => { 80 | if (err) { 81 | reject(err); 82 | } else { 83 | resolve(); 84 | } 85 | } 86 | ); 87 | }); 88 | }); 89 | 90 | it(`should create new notification with name: "${TEST_NOTIFICATION_NAME}" for device with id: "${Config.DEVICE_ID}"`, () => { 91 | const requestId = randomString.generate(); 92 | 93 | return new Promise((resolve) => { 94 | ee.once(requestId, (message) => { 95 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 96 | expect(message.notification).to.be.an(`object`); 97 | 98 | testNotificationId = message.notification.id; 99 | 100 | resolve(); 101 | }); 102 | 103 | mqttClient.publish( 104 | CONST.DH_REQUEST_TOPIC, 105 | JSON.stringify({ 106 | action: INSERT_ACTION, 107 | requestId: requestId, 108 | deviceId: Config.DEVICE_ID, 109 | notification: { 110 | notification: TEST_NOTIFICATION_NAME, 111 | parameters: TEST_NOTIFICATION_PARAMETERS, 112 | }, 113 | }) 114 | ); 115 | }); 116 | }); 117 | 118 | it(`should query the notification with name: "${TEST_NOTIFICATION_NAME}"`, () => { 119 | const requestId = randomString.generate(); 120 | 121 | return new Promise((resolve) => { 122 | ee.once(requestId, (message) => { 123 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 124 | expect(message.notification).to.be.an(`object`); 125 | expect(message.notification.id).to.equal(testNotificationId); 126 | expect(message.notification.notification).to.equal( 127 | TEST_NOTIFICATION_NAME 128 | ); 129 | expect(message.notification.deviceId).to.equal(Config.DEVICE_ID); 130 | expect(message.notification.parameters).to.deep.equal( 131 | TEST_NOTIFICATION_PARAMETERS 132 | ); 133 | 134 | resolve(); 135 | }); 136 | 137 | mqttClient.publish( 138 | CONST.DH_REQUEST_TOPIC, 139 | JSON.stringify({ 140 | action: GET_ACTION, 141 | requestId: requestId, 142 | deviceId: Config.DEVICE_ID, 143 | notificationId: testNotificationId, 144 | }) 145 | ); 146 | }); 147 | }); 148 | 149 | it(`should query the list of notifications for device with id: "${Config.DEVICE_ID}" with existing notification with name: "${TEST_NOTIFICATION_NAME}"`, () => { 150 | const requestId = randomString.generate(); 151 | 152 | return new Promise((resolve) => { 153 | ee.once(requestId, (message) => { 154 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 155 | expect(message.notifications).to.be.an(`array`); 156 | expect( 157 | message.notifications.map( 158 | (notificationObject) => notificationObject.id 159 | ) 160 | ).to.include.members([testNotificationId]); 161 | expect( 162 | message.notifications.map( 163 | (notificationObject) => notificationObject.notification 164 | ) 165 | ).to.include.members([TEST_NOTIFICATION_NAME]); 166 | 167 | resolve(); 168 | }); 169 | 170 | mqttClient.publish( 171 | CONST.DH_REQUEST_TOPIC, 172 | JSON.stringify({ 173 | action: LIST_ACTION, 174 | requestId: requestId, 175 | deviceId: Config.DEVICE_ID, 176 | take: 1000, 177 | }) 178 | ); 179 | }); 180 | }); 181 | 182 | it(`should disconnect from MQTT broker`, () => { 183 | return new Promise((resolve) => { 184 | mqttClient.end(() => { 185 | resolve(); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /test/integration/api/server.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `server`; 12 | const INFO_OPERATION = `info`; 13 | const INFO_ACTION = `${SUBJECT}/${INFO_OPERATION}`; 14 | const INFO_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INFO_ACTION}`; 15 | let mqttClient; 16 | 17 | it(`should connect to MQTT broker`, () => { 18 | return new Promise((resolve) => { 19 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 20 | username: Config.TEST_LOGIN, 21 | password: Config.TEST_PASSWORD, 22 | }); 23 | 24 | mqttClient.on(`message`, (topic, message) => { 25 | const messageObject = JSON.parse(message.toString()); 26 | 27 | ee.emit(messageObject.requestId, messageObject); 28 | }); 29 | 30 | mqttClient.on("connect", () => { 31 | resolve(); 32 | }); 33 | }); 34 | }); 35 | 36 | it(`should subscribe for "${INFO_TOPIC}" topic`, () => { 37 | return new Promise((resolve, reject) => { 38 | mqttClient.subscribe( 39 | `${INFO_TOPIC}@${mqttClient.options.clientId}`, 40 | (err) => { 41 | if (err) { 42 | reject(err); 43 | } else { 44 | resolve(); 45 | } 46 | } 47 | ); 48 | }); 49 | }); 50 | 51 | it(`should get server information`, () => { 52 | const requestId = randomString.generate(); 53 | 54 | return new Promise((resolve) => { 55 | ee.once(requestId, (message) => { 56 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 57 | expect(message).to.include.all.keys(`info`); 58 | 59 | resolve(); 60 | }); 61 | 62 | mqttClient.publish( 63 | CONST.DH_REQUEST_TOPIC, 64 | JSON.stringify({ 65 | action: INFO_ACTION, 66 | requestId: requestId, 67 | }) 68 | ); 69 | }); 70 | }); 71 | 72 | it(`should disconnect from MQTT broker`, () => { 73 | return new Promise((resolve) => { 74 | mqttClient.end(() => { 75 | resolve(); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/integration/api/subscription.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `subscription`; 12 | const LIST_OPERATION = `list`; 13 | const TEST_NAME = `testName`; 14 | const SUBSCRIPTION_COMMAND_TYPE = `command`; 15 | const SUBSCRIPTION_NOTIFICATION_TYPE = `notification`; 16 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 17 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 18 | const SUBSCRIPTION_COMMAND_TOPIC_1 = `dh/${SUBSCRIPTION_COMMAND_TYPE}/${Config.NETWORK_ID}/${Config.DEVICE_TYPE_ID}/${Config.DEVICE_ID}/${TEST_NAME}1`; 19 | const SUBSCRIPTION_COMMAND_TOPIC_2 = `dh/${SUBSCRIPTION_COMMAND_TYPE}/${Config.NETWORK_ID}/${Config.DEVICE_TYPE_ID}/${Config.DEVICE_ID}/${TEST_NAME}2`; 20 | const SUBSCRIPTION_NOTIFICATION_TOPIC_1 = `dh/${SUBSCRIPTION_NOTIFICATION_TYPE}/${Config.NETWORK_ID}/${Config.DEVICE_TYPE_ID}/${Config.DEVICE_ID}/${TEST_NAME}1`; 21 | const SUBSCRIPTION_NOTIFICATION_TOPIC_2 = `dh/${SUBSCRIPTION_NOTIFICATION_TYPE}/${Config.NETWORK_ID}/${Config.DEVICE_TYPE_ID}/${Config.DEVICE_ID}/${TEST_NAME}2`; 22 | let mqttClient; 23 | 24 | it(`should connect to MQTT broker`, () => { 25 | return new Promise((resolve) => { 26 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 27 | username: Config.TEST_LOGIN, 28 | password: Config.TEST_PASSWORD, 29 | }); 30 | 31 | mqttClient.on(`message`, (topic, message) => { 32 | const messageObject = JSON.parse(message.toString()); 33 | 34 | ee.emit(messageObject.requestId, messageObject); 35 | }); 36 | 37 | mqttClient.on("connect", () => { 38 | resolve(); 39 | }); 40 | }); 41 | }); 42 | 43 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 44 | return new Promise((resolve, reject) => { 45 | mqttClient.subscribe( 46 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 47 | (err) => { 48 | if (err) { 49 | reject(err); 50 | } else { 51 | resolve(); 52 | } 53 | } 54 | ); 55 | }); 56 | }); 57 | 58 | it(`should subscribe for "${SUBSCRIPTION_COMMAND_TOPIC_1}" topic`, () => { 59 | return new Promise((resolve, reject) => { 60 | mqttClient.subscribe( 61 | `${SUBSCRIPTION_COMMAND_TOPIC_1}@${mqttClient.options.clientId}`, 62 | (err) => { 63 | if (err) { 64 | reject(err); 65 | } else { 66 | resolve(); 67 | } 68 | } 69 | ); 70 | }); 71 | }); 72 | 73 | it(`should subscribe for "${SUBSCRIPTION_COMMAND_TOPIC_2}" topic`, () => { 74 | return new Promise((resolve, reject) => { 75 | mqttClient.subscribe( 76 | `${SUBSCRIPTION_COMMAND_TOPIC_2}@${mqttClient.options.clientId}`, 77 | (err) => { 78 | if (err) { 79 | reject(err); 80 | } else { 81 | resolve(); 82 | } 83 | } 84 | ); 85 | }); 86 | }); 87 | 88 | it(`should subscribe for "${SUBSCRIPTION_NOTIFICATION_TOPIC_1}" topic`, () => { 89 | return new Promise((resolve, reject) => { 90 | mqttClient.subscribe( 91 | `${SUBSCRIPTION_NOTIFICATION_TOPIC_1}@${mqttClient.options.clientId}`, 92 | (err) => { 93 | if (err) { 94 | reject(err); 95 | } else { 96 | resolve(); 97 | } 98 | } 99 | ); 100 | }); 101 | }); 102 | 103 | it(`should subscribe for "${SUBSCRIPTION_NOTIFICATION_TOPIC_2}" topic`, () => { 104 | return new Promise((resolve, reject) => { 105 | mqttClient.subscribe( 106 | `${SUBSCRIPTION_NOTIFICATION_TOPIC_2}@${mqttClient.options.clientId}`, 107 | (err) => { 108 | if (err) { 109 | reject(err); 110 | } else { 111 | resolve(); 112 | } 113 | } 114 | ); 115 | }); 116 | }); 117 | 118 | it(`should query the list of command subscriptions for the user login: "${Config.TEST_LOGIN}"`, () => { 119 | const requestId = randomString.generate(); 120 | 121 | return new Promise((resolve) => { 122 | ee.once(requestId, (message) => { 123 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 124 | expect(message.subscriptions).to.be.an(`array`); 125 | expect( 126 | message.subscriptions.map((subscriptionObject) => { 127 | return subscriptionObject.deviceId; 128 | }) 129 | ).to.include.members([Config.DEVICE_ID]); 130 | expect( 131 | message.subscriptions.map((subscriptionObject) => { 132 | return subscriptionObject.names[0]; 133 | }) 134 | ).to.include.members([`${TEST_NAME}1`, `${TEST_NAME}2`]); 135 | 136 | resolve(); 137 | }); 138 | 139 | mqttClient.publish( 140 | CONST.DH_REQUEST_TOPIC, 141 | JSON.stringify({ 142 | action: LIST_ACTION, 143 | requestId: requestId, 144 | type: SUBSCRIPTION_COMMAND_TYPE, 145 | }) 146 | ); 147 | }); 148 | }); 149 | 150 | it(`should query the list of notification subscriptions for the user login: "${Config.TEST_LOGIN}"`, () => { 151 | const requestId = randomString.generate(); 152 | 153 | return new Promise((resolve) => { 154 | ee.once(requestId, (message) => { 155 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 156 | expect(message.subscriptions).to.be.an(`array`); 157 | expect( 158 | message.subscriptions.map((subscriptionObject) => { 159 | return subscriptionObject.deviceId; 160 | }) 161 | ).to.include.members([Config.DEVICE_ID]); 162 | expect( 163 | message.subscriptions.map((subscriptionObject) => { 164 | return subscriptionObject.names[0]; 165 | }) 166 | ).to.include.members([`${TEST_NAME}1`, `${TEST_NAME}2`]); 167 | 168 | resolve(); 169 | }); 170 | 171 | mqttClient.publish( 172 | CONST.DH_REQUEST_TOPIC, 173 | JSON.stringify({ 174 | action: LIST_ACTION, 175 | requestId: requestId, 176 | type: SUBSCRIPTION_NOTIFICATION_TYPE, 177 | }) 178 | ); 179 | }); 180 | }); 181 | 182 | it(`should disconnect from MQTT broker`, () => { 183 | return new Promise((resolve) => { 184 | mqttClient.end(() => { 185 | resolve(); 186 | }); 187 | }); 188 | }); 189 | -------------------------------------------------------------------------------- /test/integration/api/token.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `token`; 12 | const TOKEN_OPERATION = SUBJECT; 13 | const CREATE_OPERATION = `create`; 14 | const REFRESH_OPERATION = `refresh`; 15 | const TOKEN_ACTION = TOKEN_OPERATION; 16 | const CREATE_ACTION = `${SUBJECT}/${CREATE_OPERATION}`; 17 | const REFRESH_ACTION = `${SUBJECT}/${REFRESH_OPERATION}`; 18 | const TOKEN_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${TOKEN_ACTION}`; 19 | const CREATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${CREATE_ACTION}`; 20 | const REFRESH_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${REFRESH_ACTION}`; 21 | const TEST_PAYLOAD = { 22 | userId: Config.TEST_USER_ID, 23 | networkIds: [Config.NETWORK_ID], 24 | deviceTypeIds: [Config.DEVICE_TYPE_ID], 25 | }; 26 | let mqttClient; 27 | let refreshToken; 28 | 29 | it(`should connect to MQTT broker`, () => { 30 | return new Promise((resolve) => { 31 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 32 | username: Config.TEST_LOGIN, 33 | password: Config.TEST_PASSWORD, 34 | }); 35 | 36 | mqttClient.on(`message`, (topic, message) => { 37 | const messageObject = JSON.parse(message.toString()); 38 | 39 | ee.emit(messageObject.requestId, messageObject); 40 | }); 41 | 42 | mqttClient.on("connect", () => { 43 | resolve(); 44 | }); 45 | }); 46 | }); 47 | 48 | it(`should subscribe for "${TOKEN_TOPIC}" topic`, () => { 49 | return new Promise((resolve, reject) => { 50 | mqttClient.subscribe( 51 | `${TOKEN_TOPIC}@${mqttClient.options.clientId}`, 52 | (err) => { 53 | if (err) { 54 | reject(err); 55 | } else { 56 | resolve(); 57 | } 58 | } 59 | ); 60 | }); 61 | }); 62 | 63 | it(`should subscribe for "${CREATE_TOPIC}" topic`, () => { 64 | return new Promise((resolve, reject) => { 65 | mqttClient.subscribe( 66 | `${CREATE_TOPIC}@${mqttClient.options.clientId}`, 67 | (err) => { 68 | if (err) { 69 | reject(err); 70 | } else { 71 | resolve(); 72 | } 73 | } 74 | ); 75 | }); 76 | }); 77 | 78 | it(`should subscribe for "${REFRESH_TOPIC}" topic`, () => { 79 | return new Promise((resolve, reject) => { 80 | mqttClient.subscribe( 81 | `${REFRESH_TOPIC}@${mqttClient.options.clientId}`, 82 | (err) => { 83 | if (err) { 84 | reject(err); 85 | } else { 86 | resolve(); 87 | } 88 | } 89 | ); 90 | }); 91 | }); 92 | 93 | it(`should create access and refresh tokens by login: "${Config.TEST_LOGIN}" and password" "${Config.TEST_PASSWORD}"`, () => { 94 | const requestId = randomString.generate(); 95 | 96 | return new Promise((resolve) => { 97 | ee.once(requestId, (message) => { 98 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 99 | expect(message).to.include.all.keys(`accessToken`, `refreshToken`); 100 | 101 | refreshToken = message.refreshToken; 102 | 103 | resolve(); 104 | }); 105 | 106 | mqttClient.publish( 107 | CONST.DH_REQUEST_TOPIC, 108 | JSON.stringify({ 109 | action: TOKEN_ACTION, 110 | requestId: requestId, 111 | login: Config.TEST_LOGIN, 112 | password: Config.TEST_PASSWORD, 113 | }) 114 | ); 115 | }); 116 | }); 117 | 118 | it(`should create access and refresh tokens by payload`, () => { 119 | const requestId = randomString.generate(); 120 | 121 | return new Promise((resolve) => { 122 | ee.once(requestId, (message) => { 123 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 124 | expect(message).to.include.all.keys(`accessToken`, `refreshToken`); 125 | 126 | refreshToken = message.refreshToken; 127 | 128 | resolve(); 129 | }); 130 | 131 | mqttClient.publish( 132 | CONST.DH_REQUEST_TOPIC, 133 | JSON.stringify({ 134 | action: CREATE_ACTION, 135 | requestId: requestId, 136 | payload: TEST_PAYLOAD, 137 | }) 138 | ); 139 | }); 140 | }); 141 | 142 | it(`should refresh access token by refresh token.`, () => { 143 | const requestId = randomString.generate(); 144 | 145 | return new Promise((resolve) => { 146 | ee.once(requestId, (message) => { 147 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 148 | expect(message).to.include.all.keys(`accessToken`); 149 | 150 | resolve(); 151 | }); 152 | 153 | mqttClient.publish( 154 | CONST.DH_REQUEST_TOPIC, 155 | JSON.stringify({ 156 | action: REFRESH_ACTION, 157 | requestId: requestId, 158 | refreshToken: refreshToken, 159 | }) 160 | ); 161 | }); 162 | }); 163 | 164 | it(`should disconnect from MQTT broker`, () => { 165 | return new Promise((resolve) => { 166 | mqttClient.end(() => { 167 | resolve(); 168 | }); 169 | }); 170 | }); 171 | -------------------------------------------------------------------------------- /test/integration/api/user.js: -------------------------------------------------------------------------------- 1 | const CONST = require(`../constants.json`); 2 | const Config = require(`../../config`).test.integration; 3 | const mqtt = require(`mqtt`); 4 | const EventEmitter = require("events"); 5 | const randomString = require(`randomstring`); 6 | const chai = require(`chai`); 7 | const expect = chai.expect; 8 | 9 | const ee = new EventEmitter(); 10 | 11 | const SUBJECT = `user`; 12 | const GET_OPERATION = `get`; 13 | const LIST_OPERATION = `list`; 14 | const INSERT_OPERATION = `insert`; 15 | const UPDATE_OPERATION = `update`; 16 | const DELETE_OPERATION = `delete`; 17 | const GET_CURRENT_OPERATION = `getCurrent`; 18 | const UPDATE_CURRENT_OPERATION = `updateCurrent`; 19 | const GET_NETWORK_OPERATION = `getNetwork`; 20 | const ASSIGN_NETWORK_OPERATION = `assignNetwork`; 21 | const UNASSIGN_NETWORK_OPERATION = `unassignNetwork`; 22 | const GET_ACTION = `${SUBJECT}/${GET_OPERATION}`; 23 | const LIST_ACTION = `${SUBJECT}/${LIST_OPERATION}`; 24 | const INSERT_ACTION = `${SUBJECT}/${INSERT_OPERATION}`; 25 | const UPDATE_ACTION = `${SUBJECT}/${UPDATE_OPERATION}`; 26 | const DELETE_ACTION = `${SUBJECT}/${DELETE_OPERATION}`; 27 | const GET_CURRENT_ACTION = `${SUBJECT}/${GET_CURRENT_OPERATION}`; 28 | const UPDATE_CURRENT_ACTION = `${SUBJECT}/${UPDATE_CURRENT_OPERATION}`; 29 | const GET_NETWORK_ACTION = `${SUBJECT}/${GET_NETWORK_OPERATION}`; 30 | const ASSIGN_NETWORK_ACTION = `${SUBJECT}/${ASSIGN_NETWORK_OPERATION}`; 31 | const UNASSIGN_NETWORK_ACTION = `${SUBJECT}/${UNASSIGN_NETWORK_OPERATION}`; 32 | const GET_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_ACTION}`; 33 | const LIST_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${LIST_ACTION}`; 34 | const INSERT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${INSERT_ACTION}`; 35 | const UPDATE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UPDATE_ACTION}`; 36 | const DELETE_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${DELETE_ACTION}`; 37 | const GET_CURRENT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_CURRENT_ACTION}`; 38 | const UPDATE_CURRENT_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UPDATE_CURRENT_ACTION}`; 39 | const GET_NETWORK_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${GET_NETWORK_ACTION}`; 40 | const ASSIGN_NETWORK_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${ASSIGN_NETWORK_ACTION}`; 41 | const UNASSIGN_NETWORK_TOPIC = `${CONST.DH_RESPONSE_TOPIC}/${UNASSIGN_NETWORK_ACTION}`; 42 | const TEST_USER_LOGIN = randomString.generate(); 43 | const TEST_USER_PASSWORD = `qwertyui`; 44 | const START_USER_DATA = { data: `startData` }; 45 | const UPDATED_USER_DATA = { data: `updatedData` }; 46 | let mqttClient; 47 | let testUserId; 48 | 49 | it(`should connect to MQTT broker`, () => { 50 | return new Promise((resolve) => { 51 | mqttClient = mqtt.connect(Config.MQTT_BROKER_URL, { 52 | username: Config.TEST_LOGIN, 53 | password: Config.TEST_PASSWORD, 54 | }); 55 | 56 | mqttClient.on(`message`, (topic, message) => { 57 | const messageObject = JSON.parse(message.toString()); 58 | 59 | ee.emit(messageObject.requestId, messageObject); 60 | }); 61 | 62 | mqttClient.on("connect", () => { 63 | resolve(); 64 | }); 65 | }); 66 | }); 67 | 68 | it(`should subscribe for "${GET_TOPIC}" topic`, () => { 69 | return new Promise((resolve, reject) => { 70 | mqttClient.subscribe( 71 | `${GET_TOPIC}@${mqttClient.options.clientId}`, 72 | (err) => { 73 | if (err) { 74 | reject(err); 75 | } else { 76 | resolve(); 77 | } 78 | } 79 | ); 80 | }); 81 | }); 82 | 83 | it(`should subscribe for "${LIST_TOPIC}" topic`, () => { 84 | return new Promise((resolve, reject) => { 85 | mqttClient.subscribe( 86 | `${LIST_TOPIC}@${mqttClient.options.clientId}`, 87 | (err) => { 88 | if (err) { 89 | reject(err); 90 | } else { 91 | resolve(); 92 | } 93 | } 94 | ); 95 | }); 96 | }); 97 | 98 | it(`should subscribe for "${INSERT_TOPIC}" topic`, () => { 99 | return new Promise((resolve, reject) => { 100 | mqttClient.subscribe( 101 | `${INSERT_TOPIC}@${mqttClient.options.clientId}`, 102 | (err) => { 103 | if (err) { 104 | reject(err); 105 | } else { 106 | resolve(); 107 | } 108 | } 109 | ); 110 | }); 111 | }); 112 | 113 | it(`should subscribe for "${UPDATE_TOPIC}" topic`, () => { 114 | return new Promise((resolve, reject) => { 115 | mqttClient.subscribe( 116 | `${UPDATE_TOPIC}@${mqttClient.options.clientId}`, 117 | (err) => { 118 | if (err) { 119 | reject(err); 120 | } else { 121 | resolve(); 122 | } 123 | } 124 | ); 125 | }); 126 | }); 127 | 128 | it(`should subscribe for "${DELETE_TOPIC}" topic`, () => { 129 | return new Promise((resolve, reject) => { 130 | mqttClient.subscribe( 131 | `${DELETE_TOPIC}@${mqttClient.options.clientId}`, 132 | (err) => { 133 | if (err) { 134 | reject(err); 135 | } else { 136 | resolve(); 137 | } 138 | } 139 | ); 140 | }); 141 | }); 142 | 143 | it(`should subscribe for "${GET_CURRENT_TOPIC}" topic`, () => { 144 | return new Promise((resolve, reject) => { 145 | mqttClient.subscribe( 146 | `${GET_CURRENT_TOPIC}@${mqttClient.options.clientId}`, 147 | (err) => { 148 | if (err) { 149 | reject(err); 150 | } else { 151 | resolve(); 152 | } 153 | } 154 | ); 155 | }); 156 | }); 157 | 158 | it(`should subscribe for "${UPDATE_CURRENT_TOPIC}" topic`, () => { 159 | return new Promise((resolve, reject) => { 160 | mqttClient.subscribe( 161 | `${UPDATE_CURRENT_TOPIC}@${mqttClient.options.clientId}`, 162 | (err) => { 163 | if (err) { 164 | reject(err); 165 | } else { 166 | resolve(); 167 | } 168 | } 169 | ); 170 | }); 171 | }); 172 | 173 | it(`should subscribe for "${GET_NETWORK_TOPIC}" topic`, () => { 174 | return new Promise((resolve, reject) => { 175 | mqttClient.subscribe( 176 | `${GET_NETWORK_TOPIC}@${mqttClient.options.clientId}`, 177 | (err) => { 178 | if (err) { 179 | reject(err); 180 | } else { 181 | resolve(); 182 | } 183 | } 184 | ); 185 | }); 186 | }); 187 | 188 | it(`should subscribe for "${ASSIGN_NETWORK_TOPIC}" topic`, () => { 189 | return new Promise((resolve, reject) => { 190 | mqttClient.subscribe( 191 | `${ASSIGN_NETWORK_TOPIC}@${mqttClient.options.clientId}`, 192 | (err) => { 193 | if (err) { 194 | reject(err); 195 | } else { 196 | resolve(); 197 | } 198 | } 199 | ); 200 | }); 201 | }); 202 | 203 | it(`should subscribe for "${UNASSIGN_NETWORK_TOPIC}" topic`, () => { 204 | return new Promise((resolve, reject) => { 205 | mqttClient.subscribe( 206 | `${UNASSIGN_NETWORK_TOPIC}@${mqttClient.options.clientId}`, 207 | (err) => { 208 | if (err) { 209 | reject(err); 210 | } else { 211 | resolve(); 212 | } 213 | } 214 | ); 215 | }); 216 | }); 217 | 218 | it(`should create new user with login: "${TEST_USER_LOGIN}"`, () => { 219 | const requestId = randomString.generate(); 220 | 221 | return new Promise((resolve) => { 222 | ee.once(requestId, (message) => { 223 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 224 | expect(message.user.id).to.be.a(`number`); 225 | 226 | testUserId = message.user.id; 227 | 228 | resolve(); 229 | }); 230 | 231 | mqttClient.publish( 232 | CONST.DH_REQUEST_TOPIC, 233 | JSON.stringify({ 234 | action: INSERT_ACTION, 235 | requestId: requestId, 236 | user: { 237 | login: TEST_USER_LOGIN, 238 | role: 1, 239 | status: 0, 240 | password: TEST_USER_PASSWORD, 241 | data: START_USER_DATA, 242 | introReviewed: true, 243 | }, 244 | }) 245 | ); 246 | }); 247 | }); 248 | 249 | it(`should query the list of users with existing user with login: "${TEST_USER_LOGIN}"`, () => { 250 | const requestId = randomString.generate(); 251 | 252 | return new Promise((resolve) => { 253 | ee.once(requestId, (message) => { 254 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 255 | expect( 256 | message.users.map((userObject) => userObject.login) 257 | ).to.include.members([TEST_USER_LOGIN]); 258 | 259 | resolve(); 260 | }); 261 | 262 | mqttClient.publish( 263 | CONST.DH_REQUEST_TOPIC, 264 | JSON.stringify({ 265 | action: LIST_ACTION, 266 | requestId: requestId, 267 | take: 10, 268 | }) 269 | ); 270 | }); 271 | }); 272 | 273 | it(`should query the users with login: "${TEST_USER_LOGIN}"`, () => { 274 | const requestId = randomString.generate(); 275 | 276 | return new Promise((resolve) => { 277 | ee.once(requestId, (message) => { 278 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 279 | expect(message.user.id).to.equal(testUserId); 280 | expect(message.user.login).to.equal(TEST_USER_LOGIN); 281 | expect(message.user.data).to.deep.equal(START_USER_DATA); 282 | 283 | resolve(); 284 | }); 285 | 286 | mqttClient.publish( 287 | CONST.DH_REQUEST_TOPIC, 288 | JSON.stringify({ 289 | action: GET_ACTION, 290 | requestId: requestId, 291 | userId: testUserId, 292 | }) 293 | ); 294 | }); 295 | }); 296 | 297 | it(`should update the users data with login: "${TEST_USER_LOGIN}" from old data: "${JSON.stringify( 298 | START_USER_DATA 299 | )}" to new data: "${JSON.stringify(UPDATED_USER_DATA)}"`, () => { 300 | const requestId = randomString.generate(); 301 | 302 | return new Promise((resolve) => { 303 | ee.once(requestId, (message) => { 304 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 305 | 306 | resolve(); 307 | }); 308 | 309 | mqttClient.publish( 310 | CONST.DH_REQUEST_TOPIC, 311 | JSON.stringify({ 312 | action: UPDATE_ACTION, 313 | requestId: requestId, 314 | userId: testUserId, 315 | user: { 316 | data: UPDATED_USER_DATA, 317 | }, 318 | }) 319 | ); 320 | }); 321 | }); 322 | 323 | it(`should query the users with login: "${TEST_USER_LOGIN}" with updated data: "${JSON.stringify( 324 | UPDATED_USER_DATA 325 | )}"`, () => { 326 | const requestId = randomString.generate(); 327 | 328 | return new Promise((resolve) => { 329 | ee.once(requestId, (message) => { 330 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 331 | expect(message.user.id).to.equal(testUserId); 332 | expect(message.user.login).to.equal(TEST_USER_LOGIN); 333 | expect(message.user.data).to.deep.equal(UPDATED_USER_DATA); 334 | 335 | resolve(); 336 | }); 337 | 338 | mqttClient.publish( 339 | CONST.DH_REQUEST_TOPIC, 340 | JSON.stringify({ 341 | action: GET_ACTION, 342 | requestId: requestId, 343 | userId: testUserId, 344 | }) 345 | ); 346 | }); 347 | }); 348 | 349 | it(`should assign the user with login: "${TEST_USER_LOGIN}" to network with id "${Config.NETWORK_ID}"`, () => { 350 | const requestId = randomString.generate(); 351 | 352 | return new Promise((resolve) => { 353 | ee.once(requestId, (message) => { 354 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 355 | 356 | resolve(); 357 | }); 358 | 359 | mqttClient.publish( 360 | CONST.DH_REQUEST_TOPIC, 361 | JSON.stringify({ 362 | action: ASSIGN_NETWORK_ACTION, 363 | requestId: requestId, 364 | userId: testUserId, 365 | networkId: Config.NETWORK_ID, 366 | }) 367 | ); 368 | }); 369 | }); 370 | 371 | it(`should query the network of the user with login: "${TEST_USER_LOGIN}" where the network id is: "${Config.NETWORK_ID}"`, () => { 372 | const requestId = randomString.generate(); 373 | 374 | return new Promise((resolve) => { 375 | ee.once(requestId, (message) => { 376 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 377 | expect(message.network.network.id).to.equal(Config.NETWORK_ID); 378 | 379 | resolve(); 380 | }); 381 | 382 | mqttClient.publish( 383 | CONST.DH_REQUEST_TOPIC, 384 | JSON.stringify({ 385 | action: GET_NETWORK_ACTION, 386 | requestId: requestId, 387 | userId: testUserId, 388 | networkId: Config.NETWORK_ID, 389 | }) 390 | ); 391 | }); 392 | }); 393 | 394 | it(`should unassign the user with login: "${TEST_USER_LOGIN}" from network with id "${Config.NETWORK_ID}"`, () => { 395 | const requestId = randomString.generate(); 396 | 397 | return new Promise((resolve) => { 398 | ee.once(requestId, (message) => { 399 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 400 | 401 | resolve(); 402 | }); 403 | 404 | mqttClient.publish( 405 | CONST.DH_REQUEST_TOPIC, 406 | JSON.stringify({ 407 | action: UNASSIGN_NETWORK_ACTION, 408 | requestId: requestId, 409 | userId: testUserId, 410 | networkId: Config.NETWORK_ID, 411 | }) 412 | ); 413 | }); 414 | }); 415 | 416 | it(`should check that the user with login: "${TEST_USER_LOGIN}" is unassigned from the network with id: "${Config.NETWORK_ID}"`, () => { 417 | const requestId = randomString.generate(); 418 | 419 | return new Promise((resolve) => { 420 | ee.once(requestId, (message) => { 421 | expect(message.status).to.equal(CONST.ERROR_STATUS); 422 | 423 | resolve(); 424 | }); 425 | 426 | mqttClient.publish( 427 | CONST.DH_REQUEST_TOPIC, 428 | JSON.stringify({ 429 | action: GET_NETWORK_ACTION, 430 | requestId: requestId, 431 | userId: testUserId, 432 | networkId: Config.NETWORK_ID, 433 | }) 434 | ); 435 | }); 436 | }); 437 | 438 | it(`should delete user with login: "${TEST_USER_LOGIN}"`, () => { 439 | const requestId = randomString.generate(); 440 | 441 | return new Promise((resolve) => { 442 | ee.once(requestId, (message) => { 443 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 444 | 445 | resolve(); 446 | }); 447 | 448 | mqttClient.publish( 449 | CONST.DH_REQUEST_TOPIC, 450 | JSON.stringify({ 451 | action: DELETE_ACTION, 452 | requestId: requestId, 453 | userId: testUserId, 454 | }) 455 | ); 456 | }); 457 | }); 458 | 459 | it(`should query the list of users without user with login: "${TEST_USER_LOGIN}"`, () => { 460 | const requestId = randomString.generate(); 461 | 462 | return new Promise((resolve) => { 463 | ee.once(requestId, (message) => { 464 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 465 | expect( 466 | message.users.map((userObject) => userObject.login) 467 | ).to.not.include.members([TEST_USER_LOGIN]); 468 | 469 | resolve(); 470 | }); 471 | 472 | mqttClient.publish( 473 | CONST.DH_REQUEST_TOPIC, 474 | JSON.stringify({ 475 | action: LIST_ACTION, 476 | requestId: requestId, 477 | take: 10, 478 | }) 479 | ); 480 | }); 481 | }); 482 | 483 | it(`should query the current user with login: "${Config.TEST_LOGIN}"`, () => { 484 | const requestId = randomString.generate(); 485 | 486 | return new Promise((resolve) => { 487 | ee.once(requestId, (message) => { 488 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 489 | expect(message.current.login).to.equal(Config.TEST_LOGIN); 490 | 491 | resolve(); 492 | }); 493 | 494 | mqttClient.publish( 495 | CONST.DH_REQUEST_TOPIC, 496 | JSON.stringify({ 497 | action: GET_CURRENT_ACTION, 498 | requestId: requestId, 499 | }) 500 | ); 501 | }); 502 | }); 503 | 504 | it(`should update the current user data to: "${JSON.stringify( 505 | UPDATED_USER_DATA 506 | )} "`, () => { 507 | const requestId = randomString.generate(); 508 | 509 | return new Promise((resolve) => { 510 | ee.once(requestId, (message) => { 511 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 512 | 513 | resolve(); 514 | }); 515 | 516 | mqttClient.publish( 517 | CONST.DH_REQUEST_TOPIC, 518 | JSON.stringify({ 519 | action: UPDATE_CURRENT_ACTION, 520 | requestId: requestId, 521 | user: { 522 | data: UPDATED_USER_DATA, 523 | }, 524 | }) 525 | ); 526 | }); 527 | }); 528 | 529 | it(`should query the updated current user with updated data: "${JSON.stringify( 530 | UPDATED_USER_DATA 531 | )}"`, () => { 532 | const requestId = randomString.generate(); 533 | 534 | return new Promise((resolve) => { 535 | ee.once(requestId, (message) => { 536 | expect(message.status).to.equal(CONST.SUCCESS_STATUS); 537 | expect(message.current.login).to.equal(Config.TEST_LOGIN); 538 | expect(message.current.data).to.deep.equal(UPDATED_USER_DATA); 539 | 540 | resolve(); 541 | }); 542 | 543 | mqttClient.publish( 544 | CONST.DH_REQUEST_TOPIC, 545 | JSON.stringify({ 546 | action: GET_CURRENT_ACTION, 547 | requestId: requestId, 548 | }) 549 | ); 550 | }); 551 | }); 552 | 553 | it(`should disconnect from MQTT broker`, () => { 554 | return new Promise((resolve) => { 555 | mqttClient.end(() => { 556 | resolve(); 557 | }); 558 | }); 559 | }); 560 | -------------------------------------------------------------------------------- /test/integration/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "MQTT_BROKER_URL": "mqtt://localhost:1883", 3 | "TEST_LOGIN": "dhadmin", 4 | "TEST_PASSWORD": "dhadmin_#911", 5 | "TEST_USER_ID": 1, 6 | "DEVICE_ID": "e50d6085-2aba-48e9-b1c3-73c673e414be", 7 | "DEVICE_TYPE_ID": 1, 8 | "NETWORK_ID": 1 9 | } 10 | -------------------------------------------------------------------------------- /test/integration/constants.json: -------------------------------------------------------------------------------- 1 | { 2 | "DH_RESPONSE_TOPIC": "dh/response", 3 | "DH_REQUEST_TOPIC": "dh/request", 4 | "SUCCESS_STATUS": "success", 5 | "ERROR_STATUS": "error" 6 | } 7 | -------------------------------------------------------------------------------- /test/unit/lib/SubscriptionEntry.spec.js: -------------------------------------------------------------------------------- 1 | const SubscriptionEntry = require(`../../../lib/SubscriptionEntry.js`); 2 | const chai = require(`chai`); 3 | const expect = chai.expect; 4 | 5 | describe(SubscriptionEntry.name, () => { 6 | const listOfMethods = [ 7 | `getSubscriptionCounter`, 8 | `addSubscriber`, 9 | `removeSubscriber`, 10 | `getSubscriber`, 11 | `getSubscribers`, 12 | `getSubscriptionId`, 13 | `hasSubscriber`, 14 | `hasSubscriptionId`, 15 | ]; 16 | const subscriber1 = `subscriber1`; 17 | const subscriber2 = `subscriber2`; 18 | const subscriber3 = `subscriber3`; 19 | const subscriptionId1 = `1`; 20 | const subscriptionId2 = `2`; 21 | const subscriptionId3 = `3`; 22 | 23 | it(`should be a class`, () => { 24 | expect(SubscriptionEntry).to.be.a(`Function`); 25 | }); 26 | 27 | it(`should has next methods: ${listOfMethods.join(`, `)}`, () => { 28 | listOfMethods.forEach((methodName) => { 29 | expect( 30 | new SubscriptionEntry(subscriber1, subscriptionId1)[methodName] 31 | ).to.be.a(`Function`); 32 | }); 33 | }); 34 | 35 | describe(`Basic functionality`, () => { 36 | it(`should correct handle subscription entries`, () => { 37 | const subscriptionEntry = new SubscriptionEntry( 38 | subscriber1, 39 | subscriptionId1 40 | ); 41 | 42 | subscriptionEntry.addSubscriber(subscriber2, subscriptionId2); 43 | subscriptionEntry.addSubscriber(subscriber3, subscriptionId3); 44 | 45 | expect(subscriptionEntry.hasSubscriber(subscriber1)).to.equal(true); 46 | expect(subscriptionEntry.hasSubscriber(subscriber2)).to.equal(true); 47 | expect(subscriptionEntry.hasSubscriber(subscriber3)).to.equal(true); 48 | 49 | expect( 50 | subscriptionEntry.hasSubscriptionId(subscriptionId1) 51 | ).to.equal(true); 52 | expect( 53 | subscriptionEntry.hasSubscriptionId(subscriptionId2) 54 | ).to.equal(true); 55 | expect( 56 | subscriptionEntry.hasSubscriptionId(subscriptionId3) 57 | ).to.equal(true); 58 | 59 | expect(subscriptionEntry.getSubscriptionCounter()).to.equal(3); 60 | 61 | expect(subscriptionEntry.getSubscribers()) 62 | .to.be.an("array") 63 | .with.lengthOf(3) 64 | .to.include.all.members([ 65 | subscriber1, 66 | subscriber2, 67 | subscriber3, 68 | ]); 69 | 70 | expect(subscriptionEntry.getSubscriber(subscriptionId1)).to.equal( 71 | subscriber1 72 | ); 73 | expect(subscriptionEntry.getSubscriber(subscriptionId2)).to.equal( 74 | subscriber2 75 | ); 76 | expect(subscriptionEntry.getSubscriber(subscriptionId3)).to.equal( 77 | subscriber3 78 | ); 79 | 80 | expect(subscriptionEntry.getSubscriptionId(subscriber1)).to.equal( 81 | subscriptionId1 82 | ); 83 | expect(subscriptionEntry.getSubscriptionId(subscriber2)).to.equal( 84 | subscriptionId2 85 | ); 86 | expect(subscriptionEntry.getSubscriptionId(subscriber3)).to.equal( 87 | subscriptionId3 88 | ); 89 | 90 | subscriptionEntry.removeSubscriber(subscriber1, subscriptionId1); 91 | subscriptionEntry.removeSubscriber(subscriber2, subscriptionId2); 92 | subscriptionEntry.removeSubscriber(subscriber3, subscriptionId3); 93 | 94 | expect(subscriptionEntry.hasSubscriber(subscriber1)).to.equal( 95 | false 96 | ); 97 | expect(subscriptionEntry.hasSubscriber(subscriber2)).to.equal( 98 | false 99 | ); 100 | expect(subscriptionEntry.hasSubscriber(subscriber3)).to.equal( 101 | false 102 | ); 103 | 104 | expect( 105 | subscriptionEntry.hasSubscriptionId(subscriptionId1) 106 | ).to.equal(false); 107 | expect( 108 | subscriptionEntry.hasSubscriptionId(subscriptionId2) 109 | ).to.equal(false); 110 | expect( 111 | subscriptionEntry.hasSubscriptionId(subscriptionId3) 112 | ).to.equal(false); 113 | 114 | expect(subscriptionEntry.getSubscriptionCounter()).to.equal(0); 115 | expect(subscriptionEntry.getSubscribers()) 116 | .to.be.an("array") 117 | .with.lengthOf(0); 118 | }); 119 | }); 120 | }); 121 | -------------------------------------------------------------------------------- /test/unit/lib/SubscriptionManager.spec.js: -------------------------------------------------------------------------------- 1 | const SubscriptionManager = require(`../../../lib/SubscriptionManager.js`); 2 | const sinon = require(`sinon`); 3 | const chai = require(`chai`); 4 | const expect = chai.expect; 5 | 6 | describe(SubscriptionManager.name, () => { 7 | const listOfMethods = [ 8 | `addSubjectSubscriber`, 9 | `removeSubjectSubscriber`, 10 | `hasSubscription`, 11 | `getSubscriptionExecutor`, 12 | `getSubjects`, 13 | `getAllSubjects`, 14 | `findSubject`, 15 | `findSubscriptionId`, 16 | `getSubscribers`, 17 | `getExecutionCounter`, 18 | `incExecutionCounter`, 19 | `resetExecutionCounter`, 20 | `addSubscriptionAttempt`, 21 | `getSubscriptionAttempts`, 22 | `removeSubscriptionAttempt`, 23 | `hasSubscriptionAttempt`, 24 | ]; 25 | const subscriber1 = `subscriber1`; 26 | const subscriber2 = `subscriber2`; 27 | const subscriber3 = `subscriber3`; 28 | const subject1 = `subject1`; 29 | const subject2 = `subject2`; 30 | const subject3 = `subject3`; 31 | const subject4 = `subject4`; 32 | const subscriptionId1 = `1`; 33 | const subscriptionId2 = `2`; 34 | const subscriptionId3 = `3`; 35 | const subscriptionId4 = `4`; 36 | const subscriptionId5 = `5`; 37 | const subscriptionId6 = `6`; 38 | 39 | it(`should be a class`, () => { 40 | expect(SubscriptionManager).to.be.a(`Function`); 41 | }); 42 | 43 | it(`should has next methods: ${listOfMethods.join(`, `)}`, () => { 44 | listOfMethods.forEach((methodName) => { 45 | expect(new SubscriptionManager()[methodName]).to.be.a(`Function`); 46 | }); 47 | }); 48 | 49 | describe(`Basic functionality`, () => { 50 | it(`SubscriptionAttempt`, () => { 51 | const subscriptionManager = new SubscriptionManager(); 52 | 53 | subscriptionManager.addSubscriptionAttempt(subscriber1, subject1); 54 | subscriptionManager.addSubscriptionAttempt(subscriber1, subject2); 55 | subscriptionManager.addSubscriptionAttempt(subscriber2, subject2); 56 | subscriptionManager.addSubscriptionAttempt(subscriber3, subject3); 57 | 58 | expect( 59 | subscriptionManager.hasSubscriptionAttempt( 60 | subscriber1, 61 | subject1 62 | ) 63 | ).to.equal(true); 64 | expect( 65 | subscriptionManager.hasSubscriptionAttempt( 66 | subscriber1, 67 | subject2 68 | ) 69 | ).to.equal(true); 70 | expect( 71 | subscriptionManager.hasSubscriptionAttempt( 72 | subscriber2, 73 | subject2 74 | ) 75 | ).to.equal(true); 76 | expect( 77 | subscriptionManager.hasSubscriptionAttempt( 78 | subscriber3, 79 | subject3 80 | ) 81 | ).to.equal(true); 82 | 83 | expect( 84 | subscriptionManager.getSubscriptionAttempts(subscriber1) 85 | ).to.deep.equal([subject1, subject2]); 86 | expect( 87 | subscriptionManager.getSubscriptionAttempts(subscriber2) 88 | ).to.deep.equal([subject2]); 89 | expect( 90 | subscriptionManager.getSubscriptionAttempts(subscriber3) 91 | ).to.deep.equal([subject3]); 92 | 93 | subscriptionManager.removeSubscriptionAttempt( 94 | subscriber1, 95 | subject1 96 | ); 97 | subscriptionManager.removeSubscriptionAttempt( 98 | subscriber1, 99 | subject2 100 | ); 101 | subscriptionManager.removeSubscriptionAttempt( 102 | subscriber2, 103 | subject2 104 | ); 105 | subscriptionManager.removeSubscriptionAttempt( 106 | subscriber3, 107 | subject3 108 | ); 109 | 110 | expect( 111 | subscriptionManager.hasSubscriptionAttempt( 112 | subscriber1, 113 | subject1 114 | ) 115 | ).to.equal(false); 116 | expect( 117 | subscriptionManager.hasSubscriptionAttempt( 118 | subscriber1, 119 | subject2 120 | ) 121 | ).to.equal(false); 122 | expect( 123 | subscriptionManager.hasSubscriptionAttempt( 124 | subscriber2, 125 | subject2 126 | ) 127 | ).to.equal(false); 128 | expect( 129 | subscriptionManager.hasSubscriptionAttempt( 130 | subscriber3, 131 | subject3 132 | ) 133 | ).to.equal(false); 134 | }); 135 | 136 | it(`Subscriptions`, () => { 137 | const subscriptionManager = new SubscriptionManager(); 138 | 139 | subscriptionManager.addSubjectSubscriber( 140 | subject1, 141 | subscriber1, 142 | subscriptionId1 143 | ); 144 | subscriptionManager.addSubjectSubscriber( 145 | subject2, 146 | subscriber1, 147 | subscriptionId2 148 | ); 149 | subscriptionManager.addSubjectSubscriber( 150 | subject2, 151 | subscriber2, 152 | subscriptionId3 153 | ); 154 | subscriptionManager.addSubjectSubscriber( 155 | subject3, 156 | subscriber3, 157 | subscriptionId4 158 | ); 159 | subscriptionManager.addSubjectSubscriber( 160 | subject4, 161 | subscriber3, 162 | subscriptionId5 163 | ); 164 | subscriptionManager.addSubjectSubscriber( 165 | subject1, 166 | subscriber3, 167 | subscriptionId6 168 | ); 169 | 170 | expect(subscriptionManager.hasSubscription(subject1)).to.equal( 171 | true 172 | ); 173 | expect(subscriptionManager.hasSubscription(subject2)).to.equal( 174 | true 175 | ); 176 | expect(subscriptionManager.hasSubscription(subject3)).to.equal( 177 | true 178 | ); 179 | expect(subscriptionManager.hasSubscription(subject4)).to.equal( 180 | true 181 | ); 182 | 183 | expect(subscriptionManager.getSubjects(subscriber1)) 184 | .to.be.an("array") 185 | .with.lengthOf(2) 186 | .to.include.all.members([subject1, subject2]); 187 | expect(subscriptionManager.getSubjects(subscriber2)) 188 | .to.be.an("array") 189 | .with.lengthOf(1) 190 | .to.include.all.members([subject2]); 191 | expect(subscriptionManager.getSubjects(subscriber3)) 192 | .to.be.an("array") 193 | .with.lengthOf(3) 194 | .to.include.all.members([subject1, subject3, subject4]); 195 | 196 | expect(subscriptionManager.getAllSubjects()) 197 | .to.be.an("array") 198 | .with.lengthOf(4) 199 | .to.include.all.members([ 200 | subject1, 201 | subject2, 202 | subject3, 203 | subject4, 204 | ]); 205 | 206 | expect( 207 | subscriptionManager.findSubject(subscriber3, subscriptionId4) 208 | ).to.equal(subject3); 209 | expect(subscriptionManager.findSubject(subscriber3, 0)).to.equal( 210 | `` 211 | ); 212 | 213 | expect( 214 | subscriptionManager.findSubscriptionId(subscriber3, subject3) 215 | ).to.equal(subscriptionId4); 216 | expect( 217 | subscriptionManager.findSubscriptionId(subscriber3, 0) 218 | ).to.equal(``); 219 | 220 | expect(subscriptionManager.getSubscribers(subject2)) 221 | .to.be.an("array") 222 | .with.lengthOf(2) 223 | .to.include.all.members([subscriber1, subscriber2]); 224 | 225 | subscriptionManager.removeSubjectSubscriber(subject1, subscriber1); 226 | subscriptionManager.removeSubjectSubscriber(subject2, subscriber1); 227 | subscriptionManager.removeSubjectSubscriber(subject2, subscriber2); 228 | subscriptionManager.removeSubjectSubscriber(subject3, subscriber3); 229 | subscriptionManager.removeSubjectSubscriber(subject4, subscriber3); 230 | subscriptionManager.removeSubjectSubscriber(subject1, subscriber3); 231 | 232 | expect(subscriptionManager.hasSubscription(subject1)).to.equal( 233 | false 234 | ); 235 | expect(subscriptionManager.hasSubscription(subject2)).to.equal( 236 | false 237 | ); 238 | expect(subscriptionManager.hasSubscription(subject3)).to.equal( 239 | false 240 | ); 241 | expect(subscriptionManager.hasSubscription(subject4)).to.equal( 242 | false 243 | ); 244 | 245 | expect(subscriptionManager.getSubjects(subscriber1)) 246 | .to.be.an("array") 247 | .with.lengthOf(0); 248 | expect(subscriptionManager.getSubjects(subscriber2)) 249 | .to.be.an("array") 250 | .with.lengthOf(0); 251 | expect(subscriptionManager.getSubjects(subscriber3)) 252 | .to.be.an("array") 253 | .with.lengthOf(0); 254 | }); 255 | 256 | it(`Execution counter`, () => { 257 | const subscriptionManager = new SubscriptionManager(); 258 | const subject1Cycles = 5; 259 | const subject2Cycles = 6; 260 | const subject3Cycles = 7; 261 | const subject1ExecutionHandler = sinon.spy(); 262 | const subject2ExecutionHandler = sinon.spy(); 263 | const subject3ExecutionHandler = sinon.spy(); 264 | 265 | subscriptionManager.addSubjectSubscriber( 266 | subject1, 267 | subscriber1, 268 | subscriptionId1 269 | ); 270 | subscriptionManager.addSubjectSubscriber( 271 | subject2, 272 | subscriber1, 273 | subscriptionId2 274 | ); 275 | subscriptionManager.addSubjectSubscriber( 276 | subject2, 277 | subscriber2, 278 | subscriptionId3 279 | ); 280 | subscriptionManager.addSubjectSubscriber( 281 | subject3, 282 | subscriber3, 283 | subscriptionId4 284 | ); 285 | subscriptionManager.addSubjectSubscriber( 286 | subject4, 287 | subscriber3, 288 | subscriptionId5 289 | ); 290 | subscriptionManager.addSubjectSubscriber( 291 | subject1, 292 | subscriber3, 293 | subscriptionId6 294 | ); 295 | 296 | for ( 297 | let executionCounter = 0; 298 | executionCounter < 299 | subscriptionManager.getSubscribers(subject1).length * 300 | subject1Cycles; 301 | executionCounter++ 302 | ) { 303 | subscriptionManager.getSubscriptionExecutor( 304 | subject1, 305 | subject1ExecutionHandler 306 | )(); 307 | } 308 | 309 | for ( 310 | let executionCounter = 0; 311 | executionCounter < 312 | subscriptionManager.getSubscribers(subject2).length * 313 | subject2Cycles; 314 | executionCounter++ 315 | ) { 316 | subscriptionManager.getSubscriptionExecutor( 317 | subject2, 318 | subject2ExecutionHandler 319 | )(); 320 | } 321 | 322 | for ( 323 | let executionCounter = 0; 324 | executionCounter < 325 | subscriptionManager.getSubscribers(subject3).length * 326 | subject3Cycles; 327 | executionCounter++ 328 | ) { 329 | subscriptionManager.getSubscriptionExecutor( 330 | subject3, 331 | subject3ExecutionHandler 332 | )(); 333 | } 334 | 335 | expect(subject1ExecutionHandler.callCount).to.equal(subject1Cycles); 336 | expect(subject2ExecutionHandler.callCount).to.equal(subject2Cycles); 337 | expect(subject3ExecutionHandler.callCount).to.equal(subject3Cycles); 338 | }); 339 | }); 340 | }); 341 | -------------------------------------------------------------------------------- /test/unit/lib/TopicStructure.spec.js: -------------------------------------------------------------------------------- 1 | const TopicStructure = require(`../../../lib/TopicStructure.js`); 2 | const chai = require(`chai`); 3 | const expect = chai.expect; 4 | 5 | describe(TopicStructure.name, () => { 6 | const dhStr = `dh`; 7 | const notificationStr = `notification`; 8 | const commandStr = `command`; 9 | const commandUpdateStr = `command_update`; 10 | const requestStr = `request`; 11 | const responseStr = `response`; 12 | const networkId = `12276`; 13 | const deviceTypeId = `1`; 14 | const deviceId = `VQjfBdTl0LvMVBt9RTJMOmwdqr6hWLjln1wZ`; 15 | const clientId = `clientId`; 16 | const name = `temperature`; 17 | const listOfMethods = [ 18 | `isDH`, 19 | `getDomain`, 20 | `hasOwner`, 21 | `getOwner`, 22 | `isSubscription`, 23 | `isResponse`, 24 | `isRequest`, 25 | `getAction`, 26 | `getNetworkIds`, 27 | `getDeviceTypeIds`, 28 | `getDevice`, 29 | `getNames`, 30 | `isNotification`, 31 | `isCommandInsert`, 32 | `isCommandUpdate`, 33 | ]; 34 | const notDhTopic = `not/dh/topic`; 35 | const dhNotificationTopic = `${dhStr}/${notificationStr}/${networkId}/${deviceTypeId}/${deviceId}/${name}`; 36 | const dhCommandTopic = `${dhStr}/${commandStr}/${networkId}/${deviceTypeId}/${deviceId}/${name}`; 37 | const dhCommandUpdateTopic = `${dhStr}/${commandUpdateStr}/${networkId}/${deviceTypeId}/${deviceId}/${name}`; 38 | const dhRequestTopic = `${dhStr}/${requestStr}`; 39 | const dhNotificationResponseTopic = `${dhStr}/${responseStr}/${notificationStr}@${clientId}`; 40 | 41 | it(`should be a class`, () => { 42 | expect(TopicStructure).to.be.a(`Function`); 43 | }); 44 | 45 | it(`should creates a TopicStructure object from MQTT topic string`, () => { 46 | expect(new TopicStructure()).to.be.a(`Object`); 47 | }); 48 | 49 | it(`should has next methods: ${listOfMethods.join(`, `)}`, () => { 50 | listOfMethods.forEach((methodName) => { 51 | expect(new TopicStructure()[methodName]).to.be.a(`Function`); 52 | }); 53 | }); 54 | 55 | describe(`Topic: ${notDhTopic}`, () => { 56 | const topicStructure = new TopicStructure(notDhTopic); 57 | 58 | it(`should not be a DH topic`, () => { 59 | expect(topicStructure.isDH()).to.equal(false); 60 | }); 61 | }); 62 | 63 | describe(`Topic: ${dhNotificationTopic}`, () => { 64 | const topicStructure = new TopicStructure(dhNotificationTopic); 65 | 66 | it(`should be a DH topic`, () => { 67 | expect(topicStructure.isDH()).to.equal(true); 68 | }); 69 | 70 | it(`should be a subscription topic`, () => { 71 | expect(topicStructure.isSubscription()).to.equal(true); 72 | }); 73 | 74 | it(`should be a notification/insert topic`, () => { 75 | expect(topicStructure.isNotification()).to.equal(true); 76 | }); 77 | 78 | it(`should has device id: ${deviceId}`, () => { 79 | expect(topicStructure.getDevice()).to.equal(deviceId); 80 | }); 81 | 82 | it(`should has notification name: ${name}`, () => { 83 | expect(topicStructure.getNames()).to.deep.equal([name]); 84 | }); 85 | }); 86 | 87 | describe(`Topic: ${dhCommandTopic}`, () => { 88 | const topicStructure = new TopicStructure(dhCommandTopic); 89 | 90 | it(`should be a DH topic`, () => { 91 | expect(topicStructure.isDH()).to.equal(true); 92 | }); 93 | 94 | it(`should be a subscription topic`, () => { 95 | expect(topicStructure.isSubscription()).to.equal(true); 96 | }); 97 | 98 | it(`should be a command/insert topic`, () => { 99 | expect(topicStructure.isCommandInsert()).to.equal(true); 100 | }); 101 | 102 | it(`should has device id: ${deviceId}`, () => { 103 | expect(topicStructure.getDevice()).to.equal(deviceId); 104 | }); 105 | 106 | it(`should has notification name: ${name}`, () => { 107 | expect(topicStructure.getNames()).to.deep.equal([name]); 108 | }); 109 | }); 110 | 111 | describe(`Topic: ${dhCommandUpdateTopic}`, () => { 112 | const topicStructure = new TopicStructure(dhCommandUpdateTopic); 113 | 114 | it(`should be a DH topic`, () => { 115 | expect(topicStructure.isDH()).to.equal(true); 116 | }); 117 | 118 | it(`should be a subscription topic`, () => { 119 | expect(topicStructure.isSubscription()).to.equal(true); 120 | }); 121 | 122 | it(`should be a command/update topic`, () => { 123 | expect(topicStructure.isCommandUpdate()).to.equal(true); 124 | }); 125 | 126 | it(`should has device id: ${deviceId}`, () => { 127 | expect(topicStructure.getDevice()).to.equal(deviceId); 128 | }); 129 | 130 | it(`should has notification name: ${name}`, () => { 131 | expect(topicStructure.getNames()).to.deep.equal([name]); 132 | }); 133 | }); 134 | 135 | describe(`Topic: ${dhRequestTopic}`, () => { 136 | const topicStructure = new TopicStructure(dhRequestTopic); 137 | 138 | it(`should be a DH topic`, () => { 139 | expect(topicStructure.isDH()).to.equal(true); 140 | }); 141 | 142 | it(`should be a request topic`, () => { 143 | expect(topicStructure.isRequest()).to.equal(true); 144 | }); 145 | 146 | it(`should not be a response topic`, () => { 147 | expect(topicStructure.isResponse()).to.equal(false); 148 | }); 149 | 150 | it(`should not be a subscription topic`, () => { 151 | expect(topicStructure.isSubscription()).to.equal(false); 152 | }); 153 | }); 154 | 155 | describe(`Topic: ${dhNotificationResponseTopic}`, () => { 156 | const topicStructure = new TopicStructure(dhNotificationResponseTopic); 157 | 158 | it(`should be a DH topic`, () => { 159 | expect(topicStructure.isDH()).to.equal(true); 160 | }); 161 | 162 | it(`should be a request topic`, () => { 163 | expect(topicStructure.isResponse()).to.equal(true); 164 | }); 165 | 166 | it(`should has an action: ${notificationStr}`, () => { 167 | expect(topicStructure.getAction()).to.equal(notificationStr); 168 | }); 169 | 170 | it(`should has an owner: ${clientId}`, () => { 171 | expect(topicStructure.getOwner()).to.equal(clientId); 172 | }); 173 | 174 | it(`should not be a request topic`, () => { 175 | expect(topicStructure.isRequest()).to.equal(false); 176 | }); 177 | 178 | it(`should not be a subscription topic`, () => { 179 | expect(topicStructure.isSubscription()).to.equal(false); 180 | }); 181 | }); 182 | }); 183 | -------------------------------------------------------------------------------- /test/unit/lib/WebSocket.spec.js: -------------------------------------------------------------------------------- 1 | const WebSocket = require(`../../../lib/WebSocket.js`); 2 | const WebSocketServer = require(`ws`).Server; 3 | const sinon = require(`sinon`); 4 | const chai = require(`chai`); 5 | const expect = chai.expect; 6 | 7 | describe(WebSocket.name, () => { 8 | const testMessage = "testMessage"; 9 | const WS_SERVER_PORT = 9090; 10 | const WS_SERVER_URL = `ws://127.0.0.1:${WS_SERVER_PORT}`; 11 | 12 | it(`should be a class`, () => { 13 | expect(WebSocket).to.be.a(`Function`); 14 | }); 15 | 16 | describe(`Events`, () => { 17 | let wsServer; 18 | let wsClient; 19 | 20 | beforeEach(() => { 21 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 22 | wsClient = new WebSocket(WS_SERVER_URL); 23 | }); 24 | 25 | afterEach(() => { 26 | wsClient.close(); 27 | wsServer.close(); 28 | }); 29 | 30 | it("should fire events", (done) => { 31 | const checkExpectation = () => { 32 | if ( 33 | openSpy.calledOnce && 34 | messageSpy.calledOnce && 35 | closeSpy.calledOnce 36 | ) { 37 | done(); 38 | } 39 | }; 40 | 41 | const openSpy = sinon.spy(checkExpectation); 42 | const messageSpy = sinon.spy(checkExpectation); 43 | const closeSpy = sinon.spy(checkExpectation); 44 | 45 | wsServer.on("connection", (ws) => { 46 | ws.on(`message`, (message) => { 47 | ws.send(message); 48 | }); 49 | }); 50 | 51 | wsClient.on("open", () => { 52 | openSpy(); 53 | wsClient.sendString(testMessage); 54 | }); 55 | 56 | wsClient.on("message", () => { 57 | messageSpy(); 58 | wsClient.close(); 59 | }); 60 | 61 | wsClient.on("close", () => closeSpy()); 62 | }); 63 | }); 64 | 65 | describe(`Interaction with web socket server`, () => { 66 | let wsServer; 67 | let wsClient; 68 | 69 | beforeEach(() => { 70 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 71 | wsClient = new WebSocket(WS_SERVER_URL); 72 | }); 73 | 74 | afterEach(() => { 75 | wsClient.close(); 76 | wsServer.close(); 77 | }); 78 | 79 | it(`should connect to web socket server`, (done) => { 80 | wsClient.on(`open`, () => { 81 | done(); 82 | }); 83 | }); 84 | 85 | it(`should send test string to web socket server`, (done) => { 86 | wsClient.on(`open`, () => wsClient.sendString(testMessage)); 87 | 88 | wsServer.on("connection", (ws) => { 89 | ws.on(`message`, (message) => { 90 | expect(message.toString()).to.equal(testMessage); 91 | done(); 92 | }); 93 | }); 94 | }); 95 | 96 | it(`should close the connection`, (done) => { 97 | wsClient.on(`open`, () => wsClient.close()); 98 | 99 | wsServer.on("connection", (ws) => { 100 | ws.on(`close`, () => { 101 | done(); 102 | }); 103 | }); 104 | }); 105 | }); 106 | }); 107 | -------------------------------------------------------------------------------- /test/unit/lib/WebSocketFactory.spec.js: -------------------------------------------------------------------------------- 1 | const WebSocketFactory = require(`../../../lib/WebSocketFactory.js`); 2 | const WebSocketServer = require(`ws`).Server; 3 | const sinon = require(`sinon`); 4 | const chai = require(`chai`); 5 | const expect = chai.expect; 6 | 7 | describe(WebSocketFactory.name, () => { 8 | const listOfMethods = [`getSocket`, `removeSocket`, `hasSocket`]; 9 | const testMessage = "testMessage"; 10 | const WS_SERVER_PORT = 9090; 11 | const WS_SERVER_URL = `ws://127.0.0.1:${WS_SERVER_PORT}`; 12 | const client1 = `client1`; 13 | const client2 = `client2`; 14 | const client3 = `client3`; 15 | 16 | it(`should be a class`, () => { 17 | expect(WebSocketFactory).to.be.a(`Function`); 18 | }); 19 | 20 | it(`should has next methods: ${listOfMethods.join(`, `)}`, () => { 21 | listOfMethods.forEach((methodName) => { 22 | expect(new WebSocketFactory()[methodName]).to.be.a(`Function`); 23 | }); 24 | }); 25 | 26 | describe(`Events`, () => { 27 | let wsServer; 28 | 29 | beforeEach(() => { 30 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 31 | 32 | wsServer.on(`connection`, (ws) => { 33 | ws.send(testMessage); 34 | }); 35 | }); 36 | 37 | afterEach(() => { 38 | wsServer.close(); 39 | }); 40 | 41 | it(`should fire event: "message"`, (done) => { 42 | const wsFactory = new WebSocketFactory(WS_SERVER_URL); 43 | const checkExpectation = () => { 44 | if ( 45 | wsClient1Spy.calledOnce && 46 | wsClient2Spy.calledOnce && 47 | wsClient3Spy.calledOnce 48 | ) { 49 | wsFactory.removeSocket(client1); 50 | wsFactory.removeSocket(client2); 51 | wsFactory.removeSocket(client3); 52 | 53 | done(); 54 | } 55 | }; 56 | const wsClient1Spy = sinon.spy(checkExpectation); 57 | const wsClient2Spy = sinon.spy(checkExpectation); 58 | const wsClient3Spy = sinon.spy(checkExpectation); 59 | 60 | wsFactory.on(`message`, (client, message) => { 61 | expect(message.data).to.equal(testMessage); 62 | switch (client) { 63 | case client1: 64 | wsClient1Spy(); 65 | break; 66 | case client2: 67 | wsClient2Spy(); 68 | break; 69 | case client3: 70 | wsClient3Spy(); 71 | break; 72 | } 73 | }); 74 | 75 | wsFactory.getSocket(client1); 76 | wsFactory.getSocket(client2); 77 | wsFactory.getSocket(client3); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /test/unit/lib/WebSocketManager.spec.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require(`events`); 2 | const WebSocketManager = require(`../../../lib/WebSocketManager.js`); 3 | const { WebSocketServer } = require(`ws`); 4 | const chai = require(`chai`); 5 | const expect = chai.expect; 6 | 7 | describe(WebSocketManager.name, () => { 8 | const listOfMethods = [ 9 | `hasKey`, 10 | `setTokens`, 11 | `getTokens`, 12 | `isAuthorized`, 13 | `setAuthorized`, 14 | `removeAuthorized`, 15 | `createTokens`, 16 | `authenticate`, 17 | `send`, 18 | `sendString`, 19 | `close`, 20 | ]; 21 | const testMessage = `testMessage`; 22 | const client1Id = `client1Id`; 23 | const client1AccessToken = `client1AccessToken`; 24 | const client1RefreshToken = `client1RefreshToken`; 25 | const WS_SERVER_PORT = 9090; 26 | const WS_SERVER_URL = `ws://127.0.0.1:${WS_SERVER_PORT}`; 27 | 28 | it(`should be a class`, () => { 29 | expect(WebSocketManager).to.be.a(`Function`); 30 | }); 31 | 32 | it(`should has next methods: ${listOfMethods.join(`, `)}`, () => { 33 | listOfMethods.forEach((methodName) => { 34 | expect(new WebSocketManager(WS_SERVER_URL)[methodName]).to.be.a( 35 | `Function` 36 | ); 37 | }); 38 | }); 39 | 40 | describe(`Events`, () => { 41 | const wsManager = new WebSocketManager(WS_SERVER_URL); 42 | let wsServer; 43 | 44 | beforeEach(() => { 45 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 46 | 47 | wsServer.on(`connection`, (ws) => { 48 | ws.send(testMessage); 49 | }); 50 | }); 51 | 52 | afterEach(() => { 53 | wsServer.close(); 54 | }); 55 | 56 | it('should send string over "sendString" method and fire the "message" event', () => { 57 | return new Promise((resolve, reject) => { 58 | wsManager.sendString(client1Id, testMessage); 59 | 60 | wsManager.on(`message`, (clientId, message) => { 61 | if (wsManager.hasKey(clientId)) { 62 | expect(clientId).to.equal(client1Id); 63 | expect(message.data).to.equal(testMessage); 64 | 65 | wsManager.close(client1Id); 66 | resolve(); 67 | } else { 68 | reject(new Error("WebSocketManager has no client key")); 69 | } 70 | }); 71 | }); 72 | }); 73 | }); 74 | 75 | describe(`Authorization map methods`, () => { 76 | let wsServer; 77 | 78 | beforeEach(() => { 79 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 80 | }); 81 | 82 | afterEach(() => { 83 | wsServer.close(); 84 | }); 85 | 86 | it(`should handle clients authorization key map`, (done) => { 87 | const wsManager = new WebSocketManager(WS_SERVER_URL); 88 | 89 | wsManager.sendString(client1Id, testMessage); 90 | 91 | wsServer.on(`connection`, () => { 92 | expect(wsManager.isAuthorized(client1Id)).to.equal(false); 93 | wsManager.setAuthorized(client1Id); 94 | expect(wsManager.isAuthorized(client1Id)).to.equal(true); 95 | wsManager.removeAuthorized(client1Id); 96 | expect(wsManager.isAuthorized(client1Id)).to.equal(false); 97 | 98 | wsManager.close(client1Id); 99 | done(); 100 | }); 101 | }); 102 | }); 103 | 104 | describe(`Tokens methods`, () => { 105 | let wsServer; 106 | 107 | beforeEach(() => { 108 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 109 | }); 110 | 111 | afterEach(() => { 112 | wsServer.close(); 113 | }); 114 | 115 | it(`should handle clients tokens`, (done) => { 116 | const wsManager = new WebSocketManager(WS_SERVER_URL); 117 | 118 | wsManager.sendString(client1Id, testMessage); 119 | 120 | wsServer.on(`connection`, () => { 121 | wsManager.setTokens( 122 | client1Id, 123 | client1AccessToken, 124 | client1RefreshToken 125 | ); 126 | 127 | const { accessToken, refreshToken } = 128 | wsManager.getTokens(client1Id); 129 | 130 | expect(accessToken).to.equal(client1AccessToken); 131 | expect(refreshToken).to.equal(client1RefreshToken); 132 | 133 | wsManager.close(client1Id); 134 | done(); 135 | }); 136 | }); 137 | }); 138 | 139 | describe(`Interaction with web socket server`, () => { 140 | const ee = new EventEmitter(); 141 | const wsManager = new WebSocketManager(WS_SERVER_URL); 142 | let wsServer; 143 | 144 | before(() => { 145 | wsServer = new WebSocketServer({ port: WS_SERVER_PORT }); 146 | 147 | wsServer.on(`connection`, (ws) => { 148 | ee.emit("connection"); 149 | ws.on(`message`, (message) => 150 | ee.emit(`message`, message.toString()) 151 | ); 152 | ws.on(`close`, () => ee.emit(`close`)); 153 | }); 154 | }); 155 | 156 | after(() => { 157 | wsServer.close(); 158 | }); 159 | 160 | it(`should connect to web socket server`, () => { 161 | return new Promise((resolve) => { 162 | wsManager.sendString(client1Id, testMessage); 163 | 164 | ee.once(`connection`, () => resolve()); 165 | }); 166 | }); 167 | 168 | it(`should send test string to web socket server`, () => { 169 | return new Promise((resolve) => { 170 | wsManager.sendString(client1Id, testMessage); 171 | 172 | ee.once(`message`, (message) => { 173 | expect(message).to.equal(testMessage); 174 | resolve(); 175 | }); 176 | }); 177 | }); 178 | 179 | it(`should close the connection`, () => { 180 | return new Promise((resolve) => { 181 | wsManager.close(client1Id); 182 | 183 | ee.once(`close`, () => resolve()); 184 | }); 185 | }); 186 | }); 187 | }); 188 | -------------------------------------------------------------------------------- /test/unit/util/DeviceHiveUtils.spec.js: -------------------------------------------------------------------------------- 1 | const DeviceHiveUtils = require(`../../../util/DeviceHiveUtils.js`); 2 | const chai = require(`chai`); 3 | const expect = chai.expect; 4 | 5 | describe(DeviceHiveUtils.name, () => { 6 | const staticMethodsNames = { 7 | createSubscriptionDataObject: `createSubscriptionDataObject`, 8 | isSameTopicRoot: `isSameTopicRoot`, 9 | isLessGlobalTopic: `isLessGlobalTopic`, 10 | isMoreGlobalTopic: `isMoreGlobalTopic`, 11 | getTopicSubscribeRequestAction: `getTopicSubscribeRequestAction`, 12 | getTopicUnsubscribeRequestAction: `getTopicUnsubscribeRequestAction`, 13 | getTopicSubscriptionResponseAction: `getTopicSubscriptionResponseAction`, 14 | }; 15 | 16 | it(`should be a class`, () => { 17 | expect(DeviceHiveUtils).to.be.a(`Function`); 18 | }); 19 | 20 | it(`should has next static methods: ${Object.keys(staticMethodsNames).join( 21 | `, ` 22 | )}`, () => { 23 | Object.keys(staticMethodsNames).forEach((staticMethodName) => { 24 | expect(DeviceHiveUtils[staticMethodName]).to.be.a(`Function`); 25 | }); 26 | }); 27 | 28 | describe(`Static method: ${staticMethodsNames.createSubscriptionDataObject}`, () => { 29 | const expectationObject1 = { 30 | topic: `dh/notification/12276/1/deviceId/name`, 31 | expectation: { 32 | action: `notification/subscribe`, 33 | deviceId: `deviceId`, 34 | names: [`name`], 35 | }, 36 | }; 37 | const expectationObject2 = { 38 | topic: `dh/notification/+/1/deviceId/#`, 39 | expectation: { 40 | action: `notification/subscribe`, 41 | deviceId: `deviceId`, 42 | }, 43 | }; 44 | const expectationObject3 = { 45 | topic: `dh/command/12276/1/+/temperature`, 46 | expectation: { 47 | action: `command/subscribe`, 48 | networkIds: [`12276`], 49 | deviceTypeIds: [`1`], 50 | names: [`temperature`], 51 | }, 52 | }; 53 | const expectationObject4 = { 54 | topic: `dh/command_update/12276/1/+/temperature`, 55 | expectation: { 56 | action: `command/subscribe`, 57 | networkIds: [`12276`], 58 | deviceTypeIds: [`1`], 59 | names: [`temperature`], 60 | returnUpdatedCommands: true, 61 | }, 62 | }; 63 | 64 | it(`subscription data object for "${expectationObject1.topic}"`, () => { 65 | expect( 66 | DeviceHiveUtils.createSubscriptionDataObject( 67 | expectationObject1.topic 68 | ) 69 | ).to.deep.equal(expectationObject1.expectation); 70 | }); 71 | 72 | it(`subscription data object for "${expectationObject2.topic}"`, () => { 73 | expect( 74 | DeviceHiveUtils.createSubscriptionDataObject( 75 | expectationObject2.topic 76 | ) 77 | ).to.deep.equal(expectationObject2.expectation); 78 | }); 79 | 80 | it(`subscription data object for "${expectationObject3.topic}"`, () => { 81 | expect( 82 | DeviceHiveUtils.createSubscriptionDataObject( 83 | expectationObject3.topic 84 | ) 85 | ).to.deep.equal(expectationObject3.expectation); 86 | }); 87 | 88 | it(`subscription data object for "${expectationObject4.topic}"`, () => { 89 | expect( 90 | DeviceHiveUtils.createSubscriptionDataObject( 91 | expectationObject4.topic 92 | ) 93 | ).to.deep.equal(expectationObject4.expectation); 94 | }); 95 | }); 96 | 97 | describe(`Static method: ${staticMethodsNames.isSameTopicRoot}`, () => { 98 | const topic1 = `dh/request`; 99 | const topic2 = `dh/response`; 100 | const topic3 = `dh/notification/1227/1/deviceId/#`; 101 | const topic4 = `dh/notification/+/1/deviceId/#`; 102 | const topic5 = `dh/#`; 103 | 104 | it(`"${topic1}" and "${topic2}" has different topic root`, () => { 105 | expect(DeviceHiveUtils.isSameTopicRoot(topic1, topic2)).to.equal( 106 | false 107 | ); 108 | }); 109 | 110 | it(`"${topic2}" and "${topic3}" has different topic root`, () => { 111 | expect(DeviceHiveUtils.isSameTopicRoot(topic2, topic3)).to.equal( 112 | false 113 | ); 114 | }); 115 | 116 | it(`"${topic3}" and "${topic4}" has same topic root`, () => { 117 | expect(DeviceHiveUtils.isSameTopicRoot(topic3, topic4)).to.equal( 118 | true 119 | ); 120 | }); 121 | 122 | it(`"${topic4}" and "${topic5}" has same topic root`, () => { 123 | expect(DeviceHiveUtils.isSameTopicRoot(topic4, topic5)).to.equal( 124 | true 125 | ); 126 | }); 127 | }); 128 | 129 | describe(`Static method: ${staticMethodsNames.isLessGlobalTopic}`, () => { 130 | const topic1 = `dh/notification/1227/1/deviceId/#`; 131 | const topic2 = `dh/notification/+/1/deviceId/#`; 132 | const topic3 = `dh/notification/#`; 133 | const topic4 = `dh/command_update/+/1/deviceId/#`; 134 | const topic5 = `dh/#`; 135 | const topic6 = `dh/notification/+`; 136 | 137 | it(`"${topic1}" should be less global than "${topic2}"`, () => { 138 | expect(DeviceHiveUtils.isLessGlobalTopic(topic1, topic2)).to.equal( 139 | true 140 | ); 141 | }); 142 | 143 | it(`"${topic2}" should be less global than "${topic3}"`, () => { 144 | expect(DeviceHiveUtils.isLessGlobalTopic(topic2, topic3)).to.equal( 145 | true 146 | ); 147 | }); 148 | 149 | it(`"${topic3}" should not be less global than "${topic4}"`, () => { 150 | expect(DeviceHiveUtils.isLessGlobalTopic(topic3, topic4)).to.equal( 151 | false 152 | ); 153 | }); 154 | 155 | it(`"${topic4}" should be less global than "${topic5}"`, () => { 156 | expect(DeviceHiveUtils.isLessGlobalTopic(topic4, topic5)).to.equal( 157 | true 158 | ); 159 | }); 160 | 161 | it(`"${topic3}" should be less global than "${topic6}"`, () => { 162 | expect(DeviceHiveUtils.isLessGlobalTopic(topic3, topic6)).to.equal( 163 | true 164 | ); 165 | }); 166 | }); 167 | 168 | describe(`Static method: ${staticMethodsNames.isMoreGlobalTopic}`, () => { 169 | const topic1 = `dh/notification/1227/1/deviceId/#`; 170 | const topic2 = `dh/notification/+/1/deviceId/#`; 171 | const topic3 = `dh/notification/#`; 172 | const topic4 = `dh/command_update/+/1/deviceId/#`; 173 | const topic5 = `dh/#`; 174 | 175 | it(`"${topic1}" should not be more global than "${topic2}"`, () => { 176 | expect(DeviceHiveUtils.isMoreGlobalTopic(topic1, topic2)).to.equal( 177 | false 178 | ); 179 | }); 180 | 181 | it(`"${topic3}" should be more global than "${topic2}"`, () => { 182 | expect(DeviceHiveUtils.isMoreGlobalTopic(topic3, topic2)).to.equal( 183 | true 184 | ); 185 | }); 186 | 187 | it(`"${topic3}" should not be more global than "${topic4}"`, () => { 188 | expect(DeviceHiveUtils.isMoreGlobalTopic(topic3, topic4)).to.equal( 189 | false 190 | ); 191 | }); 192 | 193 | it(`"${topic4}" should not be more global than "${topic5}"`, () => { 194 | expect(DeviceHiveUtils.isMoreGlobalTopic(topic4, topic5)).to.equal( 195 | false 196 | ); 197 | }); 198 | 199 | it(`"${topic5}" should be more global than "${topic3}"`, () => { 200 | expect(DeviceHiveUtils.isMoreGlobalTopic(topic5, topic3)).to.equal( 201 | true 202 | ); 203 | }); 204 | }); 205 | 206 | describe(`Static method: ${staticMethodsNames.getTopicSubscribeRequestAction}`, () => { 207 | const expectation1 = [ 208 | `dh/notification/1227/1/deviceId/name`, 209 | `notification/subscribe`, 210 | ]; 211 | const expectation2 = [ 212 | `dh/command/1227/1/deviceId/name`, 213 | `command/subscribe`, 214 | ]; 215 | const expectation3 = [ 216 | `dh/command_update/1227/1/deviceId/name`, 217 | `command/subscribe`, 218 | ]; 219 | const expectation4 = [`dh/response/notification`, ``]; 220 | 221 | it(`Action for "${expectation1[0]}" should be "${expectation1[1]}"`, () => { 222 | expect( 223 | DeviceHiveUtils.getTopicSubscribeRequestAction(expectation1[0]) 224 | ).to.equal(expectation1[1]); 225 | }); 226 | 227 | it(`Action for "${expectation2[0]}" should be "${expectation2[1]}"`, () => { 228 | expect( 229 | DeviceHiveUtils.getTopicSubscribeRequestAction(expectation2[0]) 230 | ).to.equal(expectation2[1]); 231 | }); 232 | 233 | it(`Action for "${expectation3[0]}" should be "${expectation3[1]}"`, () => { 234 | expect( 235 | DeviceHiveUtils.getTopicSubscribeRequestAction(expectation3[0]) 236 | ).to.equal(expectation3[1]); 237 | }); 238 | 239 | it(`Action for "${expectation4[0]}" should be "${expectation4[1]}"`, () => { 240 | expect( 241 | DeviceHiveUtils.getTopicSubscribeRequestAction(expectation4[0]) 242 | ).to.equal(expectation4[1]); 243 | }); 244 | }); 245 | 246 | describe(`Static method: ${staticMethodsNames.getTopicUnsubscribeRequestAction}`, () => { 247 | const expectation1 = [ 248 | `dh/notification/1227/1/deviceId/name`, 249 | `notification/unsubscribe`, 250 | ]; 251 | const expectation2 = [ 252 | `dh/command/1227/1/deviceId/name`, 253 | `command/unsubscribe`, 254 | ]; 255 | const expectation3 = [ 256 | `dh/command_update/1227/1/deviceId/name`, 257 | `command/unsubscribe`, 258 | ]; 259 | const expectation4 = [`dh/response/notification`, ``]; 260 | 261 | it(`Action for "${expectation1[0]}" should be "${expectation1[1]}"`, () => { 262 | expect( 263 | DeviceHiveUtils.getTopicUnsubscribeRequestAction( 264 | expectation1[0] 265 | ) 266 | ).to.equal(expectation1[1]); 267 | }); 268 | 269 | it(`Action for "${expectation2[0]}" should be "${expectation2[1]}"`, () => { 270 | expect( 271 | DeviceHiveUtils.getTopicUnsubscribeRequestAction( 272 | expectation2[0] 273 | ) 274 | ).to.equal(expectation2[1]); 275 | }); 276 | 277 | it(`Action for "${expectation3[0]}" should be "${expectation3[1]}"`, () => { 278 | expect( 279 | DeviceHiveUtils.getTopicUnsubscribeRequestAction( 280 | expectation3[0] 281 | ) 282 | ).to.equal(expectation3[1]); 283 | }); 284 | 285 | it(`Action for "${expectation4[0]}" should be "${expectation4[1]}"`, () => { 286 | expect( 287 | DeviceHiveUtils.getTopicUnsubscribeRequestAction( 288 | expectation4[0] 289 | ) 290 | ).to.equal(expectation4[1]); 291 | }); 292 | }); 293 | 294 | describe(`Static method: ${staticMethodsNames.getTopicSubscriptionResponseAction}`, () => { 295 | const expectation1 = [ 296 | `dh/notification/1227/1/deviceId/name`, 297 | `notification/insert`, 298 | ]; 299 | const expectation2 = [ 300 | `dh/command/1227/1/deviceId/name`, 301 | `command/insert`, 302 | ]; 303 | const expectation3 = [ 304 | `dh/command_update/1227/1/deviceId/name`, 305 | `command/update`, 306 | ]; 307 | const expectation4 = [`dh/request/notification`, ``]; 308 | 309 | it(`Action for "${expectation1[0]}" should be "${expectation1[1]}"`, () => { 310 | expect( 311 | DeviceHiveUtils.getTopicSubscriptionResponseAction( 312 | expectation1[0] 313 | ) 314 | ).to.equal(expectation1[1]); 315 | }); 316 | 317 | it(`Action for "${expectation2[0]}" should be "${expectation2[1]}"`, () => { 318 | expect( 319 | DeviceHiveUtils.getTopicSubscriptionResponseAction( 320 | expectation2[0] 321 | ) 322 | ).to.equal(expectation2[1]); 323 | }); 324 | 325 | it(`Action for "${expectation3[0]}" should be "${expectation3[1]}"`, () => { 326 | expect( 327 | DeviceHiveUtils.getTopicSubscriptionResponseAction( 328 | expectation3[0] 329 | ) 330 | ).to.equal(expectation3[1]); 331 | }); 332 | 333 | it(`Action for "${expectation4[0]}" should be "${expectation4[1]}"`, () => { 334 | expect( 335 | DeviceHiveUtils.getTopicSubscriptionResponseAction( 336 | expectation4[0] 337 | ) 338 | ).to.equal(expectation4[1]); 339 | }); 340 | }); 341 | }); 342 | -------------------------------------------------------------------------------- /util/DeviceHiveUtils.js: -------------------------------------------------------------------------------- 1 | const CONST = require("./constants.json"); 2 | const TopicStructure = require("../lib/TopicStructure.js"); 3 | const LRU = require("lru-cache"); 4 | const cache = new LRU({ 5 | max: 10000, 6 | ttl: 1000 * 60 * 60, 7 | }); 8 | 9 | /** 10 | * Generate the possible patterns that might match a topic. 11 | * 12 | * @param {string} topic 13 | * @return {Array} the list of the patterns 14 | */ 15 | function _topicPatterns(topic) { 16 | const parts = topic.split("/"); 17 | const patterns = [topic]; 18 | let i; 19 | const a = []; 20 | const b = []; 21 | let j; 22 | let k; 23 | let h; 24 | const list = []; 25 | 26 | for (j = 1; j < parts.length; j++) { 27 | list.length = 0; // clear the array 28 | 29 | for (i = 0; i < parts.length; i++) { 30 | a.length = 0; 31 | b.length = 0; 32 | 33 | list.push(i); 34 | for (h = 1; list.length < j; h++) { 35 | list.unshift(parts.length - h); 36 | } 37 | 38 | for (k = 0; k < parts.length; k++) { 39 | if (list.indexOf(k) >= 0) { 40 | a.push(parts[k]); 41 | b.push(parts[k]); 42 | } else { 43 | if (k === 0 || a[a.length - 1] !== "#") { 44 | a.push("#"); 45 | } 46 | b.push("+"); 47 | } 48 | } 49 | 50 | patterns.push(a.join("/")); 51 | patterns.push(b.join("/")); 52 | list.shift(); 53 | } 54 | } 55 | 56 | return patterns; 57 | } 58 | 59 | /** 60 | * Generate the possible patterns that might match a topic. 61 | * Memozied version. 62 | * 63 | * @param {String} topic 64 | * @return {Array} the list of the patterns 65 | */ 66 | function topicPatterns(topic) { 67 | let result = cache.get(topic); 68 | if (!result) { 69 | result = _topicPatterns(topic); 70 | } 71 | cache.set(topic, result); 72 | return result; 73 | } 74 | 75 | /** 76 | * Device Hive Util class 77 | */ 78 | class DeviceHiveUtils { 79 | /** 80 | * Create subscription object based on topic parameter 81 | * @param {string} topic 82 | * @return {Object} 83 | */ 84 | static createSubscriptionDataObject(topic) { 85 | const topicStructure = new TopicStructure(topic); 86 | const result = {}; 87 | const action = DeviceHiveUtils.getTopicSubscribeRequestAction(topic); 88 | const networkIds = topicStructure.getNetworkIds(); 89 | const deviceTypeIds = topicStructure.getDeviceTypeIds(); 90 | const deviceId = topicStructure.getDevice(); 91 | const names = topicStructure.getNames(); 92 | 93 | if (action) { 94 | result.action = action; 95 | } 96 | if (networkIds) { 97 | result.networkIds = networkIds; 98 | } 99 | if (deviceTypeIds) { 100 | result.deviceTypeIds = deviceTypeIds; 101 | } 102 | if (deviceId) { 103 | result.deviceId = deviceId; 104 | } 105 | if (names) { 106 | result.names = names; 107 | } 108 | if (topicStructure.isCommandUpdate()) { 109 | result.returnUpdatedCommands = true; 110 | } 111 | 112 | return result; 113 | } 114 | 115 | /** 116 | * Check for same topic root 117 | * @param {string} topic1 118 | * @param {string} topic2 119 | * @return {boolean} 120 | */ 121 | static isSameTopicRoot(topic1, topic2) { 122 | let result = true; 123 | const splittedTopic1 = topic1.split("/"); 124 | const splittedTopic2 = topic2.split("/"); 125 | const smallestSize = 126 | splittedTopic1.length < splittedTopic2.length 127 | ? splittedTopic1.length 128 | : splittedTopic2.length; 129 | 130 | for (let counter = 0; counter < smallestSize; counter++) { 131 | if ( 132 | splittedTopic1[counter] !== splittedTopic2[counter] && 133 | !( 134 | CONST.MQTT.WILDCARDS.includes(splittedTopic1[counter]) || 135 | CONST.MQTT.WILDCARDS.includes(splittedTopic2[counter]) 136 | ) 137 | ) { 138 | result = false; 139 | break; 140 | } 141 | } 142 | 143 | return result; 144 | } 145 | 146 | /** 147 | * Check if the topicToCheck is less global than the topicToCompare 148 | * @param {string} topicToCheck 149 | * @param {string} topicToCompare 150 | * @return {boolean} 151 | */ 152 | static isLessGlobalTopic(topicToCheck, topicToCompare) { 153 | let result = false; 154 | const topicToCheckPatterns = topicPatterns(topicToCheck); 155 | 156 | if (topicToCheckPatterns.includes(topicToCompare)) { 157 | result = true; 158 | } 159 | 160 | return result; 161 | } 162 | 163 | /** 164 | * Check if the topicToCheck is more global than the topicToCompare 165 | * @param {string} topicToCheck 166 | * @param {string} topicToCompare 167 | * @return {boolean} 168 | */ 169 | static isMoreGlobalTopic(topicToCheck, topicToCompare) { 170 | let result = false; 171 | const topicToComparePatterns = topicPatterns(topicToCompare); 172 | 173 | if (topicToComparePatterns.includes(topicToCheck)) { 174 | result = true; 175 | } 176 | 177 | return result; 178 | } 179 | 180 | /** 181 | * Get WS action for topic subscription 182 | * @param {string} topic 183 | * @return {string} 184 | */ 185 | static getTopicSubscribeRequestAction(topic) { 186 | let action = ""; 187 | const topicStructure = new TopicStructure(topic); 188 | 189 | if (topicStructure.isSubscription()) { 190 | if (topicStructure.isNotification()) { 191 | action = CONST.WS.ACTIONS.NOTIFICATION_SUBSCRIBE; 192 | } else if ( 193 | topicStructure.isCommandInsert() || 194 | topicStructure.isCommandUpdate() 195 | ) { 196 | action = CONST.WS.ACTIONS.COMMAND_SUBSCRIBE; 197 | } 198 | } 199 | 200 | return action; 201 | } 202 | 203 | /** 204 | * Get WS action for topic unsubscription 205 | * @param {string} topic 206 | * @return {string} 207 | */ 208 | static getTopicUnsubscribeRequestAction(topic) { 209 | let action = ""; 210 | const topicStructure = new TopicStructure(topic); 211 | 212 | if (topicStructure.isSubscription()) { 213 | if (topicStructure.isNotification()) { 214 | action = CONST.WS.ACTIONS.NOTIFICATION_UNSUBSCRIBE; 215 | } else if ( 216 | topicStructure.isCommandInsert() || 217 | topicStructure.isCommandUpdate() 218 | ) { 219 | action = CONST.WS.ACTIONS.COMMAND_UNSUBSCRIBE; 220 | } 221 | } 222 | 223 | return action; 224 | } 225 | 226 | /** 227 | * Get WS response action for topic 228 | * @param {string} topic 229 | * @return {string} 230 | */ 231 | static getTopicSubscriptionResponseAction(topic) { 232 | let action = ""; 233 | const topicStructure = new TopicStructure(topic); 234 | 235 | if (topicStructure.isSubscription()) { 236 | if (topicStructure.isNotification()) { 237 | action = CONST.WS.ACTIONS.NOTIFICATION_INSERT; 238 | } else if (topicStructure.isCommandUpdate()) { 239 | action = CONST.WS.ACTIONS.COMMAND_UPDATE; 240 | } else if (topicStructure.isCommandInsert()) { 241 | action = CONST.WS.ACTIONS.COMMAND_INSERT; 242 | } 243 | } 244 | 245 | return action; 246 | } 247 | } 248 | 249 | module.exports = DeviceHiveUtils; 250 | -------------------------------------------------------------------------------- /util/constants.json: -------------------------------------------------------------------------------- 1 | { 2 | "CLIENT_ID_TOPIC_SPLITTER": "@", 3 | "DEV": "dev", 4 | "MQTT": { 5 | "DEFAULT_PORT": 1883, 6 | "TLS_DEFAULT_PORT": 8443, 7 | "WILDCARDS": ["+", "#"], 8 | "SYS_PREFIX": "$SYS", 9 | "BROKER_STATS_TOPICS": { 10 | "UPTIME": "uptime", 11 | "TIME": "time", 12 | "CLIENTS_TOTAL": "clients/total", 13 | "CLIENTS_MAXIMUM": "clients/maximum", 14 | "PUBLISH_SENT": "messages/publish/sent", 15 | "MEMORY_HEAP_CURRENT": "memory/heap/current", 16 | "MEMORY_HEAP_MAXIMUM": "memory/heap/maximum", 17 | "CPU_USAGE": "cpu/usage", 18 | "CPU_AVG_1": "cpu/avg/last/1", 19 | "CPU_AVG_5": "cpu/avg/last/5", 20 | "CPU_AVG_15": "cpu/avg/last/15" 21 | } 22 | }, 23 | "PERSISTENCE": { 24 | "REDIS_DEV_HOST": "localhost", 25 | "REDIS_DEV_PORT": "6379", 26 | "MAX_NUMBER_OF_SUBSCRIPTIONS": 600000, 27 | "MAX_NUMBER_OF_PACKETS": 600000 28 | }, 29 | "WS": { 30 | "SUCCESS_STATUS": "success", 31 | "ACTIONS": { 32 | "AUTHENTICATE": "authenticate", 33 | "TOKEN": "token", 34 | "NOTIFICATION_SUBSCRIBE": "notification/subscribe", 35 | "NOTIFICATION_UNSUBSCRIBE": "notification/unsubscribe", 36 | "NOTIFICATION_INSERT": "notification/insert", 37 | "COMMAND_SUBSCRIBE": "command/subscribe", 38 | "COMMAND_UNSUBSCRIBE": "command/unsubscribe", 39 | "COMMAND_INSERT": "command/insert", 40 | "COMMAND_UPDATE": "command/update" 41 | } 42 | }, 43 | "TOPICS": { 44 | "TOKEN": "dh/request/token", 45 | "AUTHENTICATE": "dh/request/authenticate", 46 | "PARTS": { 47 | "DH": "dh", 48 | "RESPONSE": "response", 49 | "REQUEST": "request", 50 | "NOTIFICATION": "notification", 51 | "COMMAND": "command", 52 | "COMMAND_UPDATE": "command_update", 53 | "TOKEN": "token", 54 | "AUTHENTICATE": "authenticate" 55 | }, 56 | "FORBIDDEN": { 57 | "START_WITH": ["dh/request"] 58 | } 59 | } 60 | } 61 | --------------------------------------------------------------------------------