inital commit
This commit is contained in:
Родитель
9d8f2827c0
Коммит
362d0ee4e2
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"sourceType": "module",
|
||||
"ecmaVersion": 2017
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"error",
|
||||
2
|
||||
],
|
||||
"linebreak-style": [
|
||||
"off",
|
||||
"unix"
|
||||
],
|
||||
"quotes": [
|
||||
"error",
|
||||
"single"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{ "argsIgnorePattern": "next" }
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
FROM node:8-slim
|
||||
|
||||
WORKDIR /app/
|
||||
|
||||
COPY package*.json ./
|
||||
|
||||
RUN npm install --production
|
||||
|
||||
COPY *.js ./
|
||||
|
||||
USER node
|
||||
|
||||
ENV DEBUG_OPTION " "
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["sh", "-c", "node $DEBUG_OPTION server.js"]
|
|
@ -0,0 +1,45 @@
|
|||
'use strict';
|
||||
|
||||
const winston = require('winston');
|
||||
const fse = require('fs-extra');
|
||||
|
||||
winston.loggers.add('log', {
|
||||
console: {
|
||||
level: 'info',
|
||||
handleExceptions: true,
|
||||
humanReadableUnhandledException: true
|
||||
}
|
||||
});
|
||||
|
||||
const labelName = 'PrintInput';
|
||||
const printer = 'printer';
|
||||
const filePath = process.env.ModuleOutputFile;
|
||||
if (filePath) {
|
||||
const exist = fse.existsSync(filePath);
|
||||
if (exist) {
|
||||
winston.loggers.add(printer, {
|
||||
console: {
|
||||
level: 'info',
|
||||
label: labelName
|
||||
},
|
||||
file: {
|
||||
filename: filePath,
|
||||
json: false,
|
||||
level: 'info',
|
||||
label: labelName
|
||||
}
|
||||
});
|
||||
} else {
|
||||
winston.loggers.add(printer, {
|
||||
console: {
|
||||
level: 'info',
|
||||
label: labelName
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
log: winston.loggers.get('log'),
|
||||
printer: winston.loggers.get(printer)
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"$schema-version": "0.0.1",
|
||||
"description": "",
|
||||
"image": {
|
||||
"repository": "adashen/iot-edge-testing-utility",
|
||||
"tag": {
|
||||
"version": "0.0.1",
|
||||
"platforms": {
|
||||
"amd64": "./Dockerfile"
|
||||
}
|
||||
}
|
||||
},
|
||||
"language": "node"
|
||||
}
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"name": "iot-edge-testing-utility",
|
||||
"version": "0.0.1",
|
||||
"description": "Testing utility for Azure IoT Edge Modules",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"lint": "eslint **/*.js",
|
||||
"test": "mocha --timeout 10000"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Azure/iot-edge-testing-utility.git"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Azure/iot-edge-testing-utility/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Azure/iot-edge-testing-utility#readme",
|
||||
"dependencies": {
|
||||
"azure-iot-device": "1.4.0-modules-preview",
|
||||
"azure-iot-device-mqtt": "1.4.0-modules-preview",
|
||||
"body-parser": "^1.18.2",
|
||||
"express": "^4.16.3",
|
||||
"fs-extra": "^5.0.0",
|
||||
"winston": "^2.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"chai-http": "^4.0.0",
|
||||
"eslint": "^4.19.1",
|
||||
"mocha": "^5.1.1",
|
||||
"sinon": "^5.0.7"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const utilityModule = require('./utilityModule');
|
||||
const logger = require('./logger').log;
|
||||
const http = require('http');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
utilityModule.initModule();
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
const router = express.Router();
|
||||
|
||||
router.use(function log(req, res, next) {
|
||||
logger.info(`Time: ${Date.now()} ${req.method} ${req.path}`);
|
||||
next();
|
||||
});
|
||||
|
||||
router.post('/api/v1/messages', async(req, res, next) => {
|
||||
try {
|
||||
await utilityModule.sendMessage(req.body);
|
||||
res.status(202).json({'message': 'accepted'});
|
||||
} catch(err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
|
||||
app.use('/', router);
|
||||
|
||||
app.use('*', (req, res) => {
|
||||
res.status(404).json({'error': 'I don\'t have that'});
|
||||
});
|
||||
|
||||
app.use('*', (err, req, res, next) => {
|
||||
if (err) {
|
||||
res.status(500).json({'error': err.toString()});
|
||||
} else {
|
||||
res.status(404).json({'error': 'I don\'t have that'});
|
||||
}
|
||||
});
|
||||
|
||||
const server = http.createServer(app);
|
||||
const port = process.env.PORT || 3000;
|
||||
server.listen(port);
|
||||
server.on('listening', () => {
|
||||
if (process.send) {
|
||||
process.send('online');
|
||||
}
|
||||
|
||||
logger.info(`app listening on ${port}`);
|
||||
});
|
||||
|
||||
module.exports = server;
|
|
@ -0,0 +1,110 @@
|
|||
/* eslint-env mocha */
|
||||
|
||||
process.env.NODE_ENV = 'test';
|
||||
const utilityModule = require('../utilityModule');
|
||||
|
||||
const chai = require('chai');
|
||||
const chaiHttp = require('chai-http');
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
const should = chai.should();
|
||||
const expect = chai.expect;
|
||||
const sinon = require('sinon');
|
||||
const assert = require('assert');
|
||||
chai.use(chaiHttp);
|
||||
|
||||
describe('server', () => {
|
||||
sinon.stub(utilityModule, 'initModule').callsFake(() => {});
|
||||
|
||||
const server = require('../server');
|
||||
|
||||
describe('/POST message', () => {
|
||||
it('it should invoke the sendOutputEvent', (done) => {
|
||||
const stub = sinon.stub(utilityModule, 'sendOutputEvent').callsFake(() => {});
|
||||
chai.request(server)
|
||||
.post('/api/v1/messages')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
'inputName': 'input1',
|
||||
'data': 'Hello World',
|
||||
'properties': {
|
||||
'test': true
|
||||
},
|
||||
'messageId': '0',
|
||||
'correlationId': '1'
|
||||
})
|
||||
.end((err, res) => {
|
||||
assert(stub.calledOnce);
|
||||
assert.equal(stub.args[0][0], 'input1');
|
||||
assert.equal(stub.args[0][1]['data'], 'Hello World');
|
||||
assert.equal(stub.args[0][1]['messageId'], '0');
|
||||
assert.equal(stub.args[0][1]['correlationId'], '1');
|
||||
res.should.have.status(202);
|
||||
stub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('it should show correct error if inputName is missing', (done) => {
|
||||
const stub = sinon.stub(utilityModule, 'sendOutputEvent').callsFake(() => {});
|
||||
chai.request(server)
|
||||
.post('/api/v1/messages')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
'channel': 'input1',
|
||||
'data': 'Hello World',
|
||||
'properties': {
|
||||
'test': true
|
||||
},
|
||||
'messageId': '0',
|
||||
'correlationId': '1'
|
||||
})
|
||||
.end((err, res) => {
|
||||
assert(!stub.called);
|
||||
res.should.have.status(500);
|
||||
expect(res).to.be.json;
|
||||
assert(res.body['error'].includes('Cannot get inputName'));
|
||||
stub.restore();
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('it should return 500 if error happen', (done) => {
|
||||
chai.request(server)
|
||||
.post('/api/v1/messages')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
'inputName': 'input1',
|
||||
'data': 'Hello World',
|
||||
'properties': {
|
||||
'test': true
|
||||
},
|
||||
'messageId': '0',
|
||||
'correlationId': '1'
|
||||
})
|
||||
.end((err, res) => {
|
||||
res.should.have.status(500);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('it should return 404 if no resource', (done) => {
|
||||
sinon.restore();
|
||||
chai.request(server)
|
||||
.post('/api/v1/test')
|
||||
.set('Content-Type', 'application/json')
|
||||
.send({
|
||||
'inputName': 'input1',
|
||||
'data': 'Hello World',
|
||||
'properties': {
|
||||
'test': true
|
||||
},
|
||||
'messageId': '0',
|
||||
'correlationId': '1'
|
||||
})
|
||||
.end((err, res) => {
|
||||
res.should.have.status(404);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
'use strict';
|
||||
|
||||
var Mqtt = require('azure-iot-device-mqtt').Mqtt;
|
||||
var Client = require('azure-iot-device').Client;
|
||||
var Message = require('azure-iot-device').Message;
|
||||
var fse = require('fs-extra');
|
||||
var logger = require('./logger').log;
|
||||
var printer = require('./logger').printer;
|
||||
|
||||
var client = undefined;
|
||||
|
||||
async function setOptions(optionObj) {
|
||||
return await new Promise((resolve, reject) => {
|
||||
client.setOptions(optionObj, (err) => {
|
||||
if(err) {
|
||||
logger.error(`Could not connect: ${err.message}`);
|
||||
return reject(new Error(err.toString()));
|
||||
} else {
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function printInput(inputName, msg) {
|
||||
if (inputName === 'printInput') {
|
||||
const message = msg.getBytes().toString('utf-8');
|
||||
printer.info(message);
|
||||
}
|
||||
}
|
||||
|
||||
async function initModule() {
|
||||
const connectionString = process.env.EdgeHubConnectionString;
|
||||
const caFilePath = process.env.EdgeModuleCACertificateFile;
|
||||
if (!connectionString || !caFilePath) {
|
||||
throw new Error('cannot get connectionstring or module CA Certificate File for test module');
|
||||
}
|
||||
const caContent = await fse.readFile(caFilePath);
|
||||
client = Client.fromConnectionString(connectionString, Mqtt);
|
||||
client.on('error', (err) => {
|
||||
logger.error(err.message);
|
||||
});
|
||||
await setOptions({
|
||||
ca: caContent
|
||||
});
|
||||
logger.info('Client connected');
|
||||
|
||||
client.on('inputMessage', (inputName, msg) => {
|
||||
client.complete(msg, (err) => {
|
||||
if (err) {
|
||||
logger.error(`Complete message fail with error: ${err.message}`);
|
||||
}
|
||||
});
|
||||
printInput(inputName, msg);
|
||||
});
|
||||
}
|
||||
|
||||
async function sendOutputEvent(channel, message) {
|
||||
if (!client) {
|
||||
throw new Error('Module has not been initialized');
|
||||
}
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
client.sendOutputEvent(channel, message, (err) => {
|
||||
if(err) {
|
||||
logger.error(`Send message fail: ${err.message}`);
|
||||
return reject(new Error(err.toString()));
|
||||
} else {
|
||||
logger.info('Send message successfullly');
|
||||
return resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getValidateMessage(requestBody) {
|
||||
if (!requestBody) {
|
||||
throw new Error('No request body provided');
|
||||
}
|
||||
|
||||
const channel = requestBody.inputName;
|
||||
if (!channel) {
|
||||
throw new Error('Cannot get inputName in request body.');
|
||||
}
|
||||
|
||||
const data = requestBody.data;
|
||||
if (!data) {
|
||||
throw new Error('Cannot find message data in request body');
|
||||
}
|
||||
|
||||
const message = new Message(data);
|
||||
if(requestBody.properties && typeof requestBody.properties === 'object') {
|
||||
const properties = requestBody.properties;
|
||||
for(const property in properties) {
|
||||
if (properties.hasOwnProperty(property)) {
|
||||
message.properties.add(property, properties[property]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (requestBody.messageId) {
|
||||
message.messageId = requestBody.messageId;
|
||||
}
|
||||
|
||||
if (requestBody.correlationId) {
|
||||
message.correlationId = requestBody.correlationId;
|
||||
}
|
||||
|
||||
if (requestBody.userId) {
|
||||
message.userId = requestBody.userId;
|
||||
}
|
||||
|
||||
return {channel, message};
|
||||
}
|
||||
|
||||
async function sendMessage(requestBody) {
|
||||
const {channel, message} = getValidateMessage(requestBody);
|
||||
await this.sendOutputEvent(channel, message);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
initModule: initModule,
|
||||
sendMessage: sendMessage,
|
||||
getValidateMessage: getValidateMessage,
|
||||
sendOutputEvent: sendOutputEvent
|
||||
};
|
Загрузка…
Ссылка в новой задаче