From 10d8ca30b042eada12ddf0370c8d696c3e042c44 Mon Sep 17 00:00:00 2001 From: mblackstock Date: Thu, 1 Mar 2018 20:41:16 -0800 Subject: [PATCH 01/40] use node-red-node-test-helper for node tests --- package.json | 3 +- test/nodes/core/analysis/72-sentiment_spec.js | 2 +- test/nodes/core/core/20-inject_spec.js | 2 +- test/nodes/core/core/25-catch_spec.js | 2 +- test/nodes/core/core/25-status_spec.js | 2 +- test/nodes/core/core/58-debug_spec.js | 2 +- test/nodes/core/core/60-link_spec.js | 2 +- test/nodes/core/core/75-exec_spec.js | 2 +- test/nodes/core/core/80-function_spec.js | 2 +- test/nodes/core/core/80-template_spec.js | 2 +- test/nodes/core/core/89-delay_spec.js | 2 +- test/nodes/core/core/89-trigger_spec.js | 2 +- test/nodes/core/core/90-comment_spec.js | 2 +- test/nodes/core/core/98-unknown_spec.js | 2 +- test/nodes/core/io/21-httprequest_spec.js | 2 +- test/nodes/core/io/22-websocket_spec.js | 2 +- test/nodes/core/io/23-watch_spec.js | 2 +- test/nodes/core/io/31-tcpin_spec.js | 2 +- test/nodes/core/io/31-tcprequest_spec.js | 2 +- test/nodes/core/io/32-udpin_spec.js | 2 +- test/nodes/core/io/32-udpout_spec.js | 2 +- test/nodes/core/logic/10-switch_spec.js | 2 +- test/nodes/core/logic/15-change_spec.js | 2 +- test/nodes/core/logic/16-range_spec.js | 2 +- test/nodes/core/logic/17-split_spec.js | 2 +- test/nodes/core/logic/18-sort_spec.js | 2 +- test/nodes/core/logic/19-batch_spec.js | 2 +- test/nodes/core/parsers/70-CSV_spec.js | 2 +- test/nodes/core/parsers/70-HTML_spec.js | 2 +- test/nodes/core/parsers/70-JSON_spec.js | 2 +- test/nodes/core/parsers/70-XML_spec.js | 2 +- test/nodes/core/parsers/70-YAML_spec.js | 2 +- test/nodes/core/storage/28-tail_spec.js | 2 +- test/nodes/core/storage/50-file_spec.js | 2 +- test/nodes/helper.js | 168 ------------------ 35 files changed, 35 insertions(+), 202 deletions(-) delete mode 100644 test/nodes/helper.js diff --git a/package.json b/package.json index 2c816569a..307f147b3 100644 --- a/package.json +++ b/package.json @@ -106,7 +106,8 @@ "wdio-chromedriver-service": "^0.1.1", "wdio-mocha-framework": "^0.5.11", "wdio-spec-reporter": "^0.1.3", - "webdriverio": "^4.9.11" + "webdriverio": "^4.9.11", + "node-red-node-test-helper": "0.1.5" }, "engines": { "node": ">=4" diff --git a/test/nodes/core/analysis/72-sentiment_spec.js b/test/nodes/core/analysis/72-sentiment_spec.js index 3a6ae07b1..851502704 100644 --- a/test/nodes/core/analysis/72-sentiment_spec.js +++ b/test/nodes/core/analysis/72-sentiment_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var sentimentNode = require("../../../../nodes/core/analysis/72-sentiment.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('sentiment Node', function() { diff --git a/test/nodes/core/core/20-inject_spec.js b/test/nodes/core/core/20-inject_spec.js index a42520a68..7adde0598 100644 --- a/test/nodes/core/core/20-inject_spec.js +++ b/test/nodes/core/core/20-inject_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var injectNode = require("../../../../nodes/core/core/20-inject.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('inject node', function() { diff --git a/test/nodes/core/core/25-catch_spec.js b/test/nodes/core/core/25-catch_spec.js index 51b326e2a..6caf72259 100644 --- a/test/nodes/core/core/25-catch_spec.js +++ b/test/nodes/core/core/25-catch_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var catchNode = require("../../../../nodes/core/core/25-catch.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('catch Node', function() { diff --git a/test/nodes/core/core/25-status_spec.js b/test/nodes/core/core/25-status_spec.js index ef89fd9bb..0bf38edb2 100644 --- a/test/nodes/core/core/25-status_spec.js +++ b/test/nodes/core/core/25-status_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var catchNode = require("../../../../nodes/core/core/25-status.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('status Node', function() { diff --git a/test/nodes/core/core/58-debug_spec.js b/test/nodes/core/core/58-debug_spec.js index e1d6cd3a9..1c7e25929 100644 --- a/test/nodes/core/core/58-debug_spec.js +++ b/test/nodes/core/core/58-debug_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var debugNode = require("../../../../nodes/core/core/58-debug.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var WebSocket = require('ws'); describe('debug node', function() { diff --git a/test/nodes/core/core/60-link_spec.js b/test/nodes/core/core/60-link_spec.js index c8fe9e645..d6c698d6d 100644 --- a/test/nodes/core/core/60-link_spec.js +++ b/test/nodes/core/core/60-link_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var linkNode = require("../../../../nodes/core/core/60-link.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('link Node', function() { diff --git a/test/nodes/core/core/75-exec_spec.js b/test/nodes/core/core/75-exec_spec.js index 28f6eb00f..8a4ca332a 100644 --- a/test/nodes/core/core/75-exec_spec.js +++ b/test/nodes/core/core/75-exec_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var sinon = require("sinon"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var execNode = require("../../../../nodes/core/core/75-exec.js"); var osType = require("os").type(); diff --git a/test/nodes/core/core/80-function_spec.js b/test/nodes/core/core/80-function_spec.js index a59be3076..d0b21d6e2 100644 --- a/test/nodes/core/core/80-function_spec.js +++ b/test/nodes/core/core/80-function_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var functionNode = require("../../../../nodes/core/core/80-function.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('function node', function() { diff --git a/test/nodes/core/core/80-template_spec.js b/test/nodes/core/core/80-template_spec.js index 2d2c6b65b..9a45ece57 100644 --- a/test/nodes/core/core/80-template_spec.js +++ b/test/nodes/core/core/80-template_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var templateNode = require("../../../../nodes/core/core/80-template.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('template node', function() { diff --git a/test/nodes/core/core/89-delay_spec.js b/test/nodes/core/core/89-delay_spec.js index 35ad19ede..1f6489484 100644 --- a/test/nodes/core/core/89-delay_spec.js +++ b/test/nodes/core/core/89-delay_spec.js @@ -17,7 +17,7 @@ var should = require("should"); var delayNode = require("../../../../nodes/core/core/89-delay.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var GRACE_PERCENTAGE=10; diff --git a/test/nodes/core/core/89-trigger_spec.js b/test/nodes/core/core/89-trigger_spec.js index f7aa3b693..117da1de7 100644 --- a/test/nodes/core/core/89-trigger_spec.js +++ b/test/nodes/core/core/89-trigger_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var sinon = require("sinon"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var triggerNode = require("../../../../nodes/core/core/89-trigger.js"); var RED = require("../../../../red/red.js"); diff --git a/test/nodes/core/core/90-comment_spec.js b/test/nodes/core/core/90-comment_spec.js index e9e582dd0..f5d2c5f3d 100644 --- a/test/nodes/core/core/90-comment_spec.js +++ b/test/nodes/core/core/90-comment_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var commentNode = require("../../../../nodes/core/core/90-comment.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('comment Node', function() { diff --git a/test/nodes/core/core/98-unknown_spec.js b/test/nodes/core/core/98-unknown_spec.js index 7751c5a12..c544d1c08 100644 --- a/test/nodes/core/core/98-unknown_spec.js +++ b/test/nodes/core/core/98-unknown_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var unknown = require("../../../../nodes/core/core/98-unknown.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('unknown Node', function() { diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index 060e8fcad..017bd2c2f 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -19,7 +19,7 @@ var http = require("http"); var should = require("should"); var express = require("express"); var bodyParser = require('body-parser'); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var httpRequestNode = require("../../../../nodes/core/io/21-httprequest.js"); var hashSum = require("hash-sum"); diff --git a/test/nodes/core/io/22-websocket_spec.js b/test/nodes/core/io/22-websocket_spec.js index ded27a6e3..c928c8a38 100644 --- a/test/nodes/core/io/22-websocket_spec.js +++ b/test/nodes/core/io/22-websocket_spec.js @@ -17,7 +17,7 @@ var ws = require("ws"); var when = require("when"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var websocketNode = require("../../../../nodes/core/io/22-websocket.js"); var sockets = []; diff --git a/test/nodes/core/io/23-watch_spec.js b/test/nodes/core/io/23-watch_spec.js index 39d8733b7..8f6506c57 100644 --- a/test/nodes/core/io/23-watch_spec.js +++ b/test/nodes/core/io/23-watch_spec.js @@ -17,7 +17,7 @@ var fs = require("fs-extra"); var path = require("path"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var watchNode = require("../../../../nodes/core/io/23-watch.js"); diff --git a/test/nodes/core/io/31-tcpin_spec.js b/test/nodes/core/io/31-tcpin_spec.js index a522d308a..faa74a778 100644 --- a/test/nodes/core/io/31-tcpin_spec.js +++ b/test/nodes/core/io/31-tcpin_spec.js @@ -16,7 +16,7 @@ var net = require("net"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js"); diff --git a/test/nodes/core/io/31-tcprequest_spec.js b/test/nodes/core/io/31-tcprequest_spec.js index bc9a627e0..4301e8621 100644 --- a/test/nodes/core/io/31-tcprequest_spec.js +++ b/test/nodes/core/io/31-tcprequest_spec.js @@ -16,7 +16,7 @@ var net = require("net"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var tcpinNode = require("../../../../nodes/core/io/31-tcpin.js"); diff --git a/test/nodes/core/io/32-udpin_spec.js b/test/nodes/core/io/32-udpin_spec.js index bc5772f57..e93a4d4b6 100644 --- a/test/nodes/core/io/32-udpin_spec.js +++ b/test/nodes/core/io/32-udpin_spec.js @@ -16,7 +16,7 @@ var dgram = require("dgram"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var udpNode = require("../../../../nodes/core/io/32-udp.js"); diff --git a/test/nodes/core/io/32-udpout_spec.js b/test/nodes/core/io/32-udpout_spec.js index 6f979f077..e3f77ac41 100644 --- a/test/nodes/core/io/32-udpout_spec.js +++ b/test/nodes/core/io/32-udpout_spec.js @@ -16,7 +16,7 @@ var dgram = require("dgram"); var should = require("should"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var udpNode = require("../../../../nodes/core/io/32-udp.js"); diff --git a/test/nodes/core/logic/10-switch_spec.js b/test/nodes/core/logic/10-switch_spec.js index 4d4b051ea..3625a9aad 100644 --- a/test/nodes/core/logic/10-switch_spec.js +++ b/test/nodes/core/logic/10-switch_spec.js @@ -17,7 +17,7 @@ var should = require("should"); var switchNode = require("../../../../nodes/core/logic/10-switch.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var RED = require("../../../../red/red.js"); describe('switch Node', function() { diff --git a/test/nodes/core/logic/15-change_spec.js b/test/nodes/core/logic/15-change_spec.js index 3cf1da076..fd30d54d4 100644 --- a/test/nodes/core/logic/15-change_spec.js +++ b/test/nodes/core/logic/15-change_spec.js @@ -17,7 +17,7 @@ var should = require("should"); var changeNode = require("../../../../nodes/core/logic/15-change.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('change Node', function() { diff --git a/test/nodes/core/logic/16-range_spec.js b/test/nodes/core/logic/16-range_spec.js index 2d1c28bf2..5a9c12447 100644 --- a/test/nodes/core/logic/16-range_spec.js +++ b/test/nodes/core/logic/16-range_spec.js @@ -17,7 +17,7 @@ var should = require("should"); var rangeNode = require("../../../../nodes/core/logic/16-range.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('range Node', function() { diff --git a/test/nodes/core/logic/17-split_spec.js b/test/nodes/core/logic/17-split_spec.js index 296e942d5..188640b3b 100644 --- a/test/nodes/core/logic/17-split_spec.js +++ b/test/nodes/core/logic/17-split_spec.js @@ -17,7 +17,7 @@ var should = require("should"); var splitNode = require("../../../../nodes/core/logic/17-split.js"); var joinNode = require("../../../../nodes/core/logic/17-split.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var RED = require("../../../../red/red.js"); describe('SPLIT node', function() { diff --git a/test/nodes/core/logic/18-sort_spec.js b/test/nodes/core/logic/18-sort_spec.js index d75e63767..f6c08d1f6 100644 --- a/test/nodes/core/logic/18-sort_spec.js +++ b/test/nodes/core/logic/18-sort_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var sortNode = require("../../../../nodes/core/logic/18-sort.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var RED = require("../../../../red/red.js"); describe('SORT node', function() { diff --git a/test/nodes/core/logic/19-batch_spec.js b/test/nodes/core/logic/19-batch_spec.js index 5593c5c97..5a7bfe24b 100644 --- a/test/nodes/core/logic/19-batch_spec.js +++ b/test/nodes/core/logic/19-batch_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var batchNode = require("../../../../nodes/core/logic/19-batch.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); var RED = require("../../../../red/red.js"); describe('BATCH node', function() { diff --git a/test/nodes/core/parsers/70-CSV_spec.js b/test/nodes/core/parsers/70-CSV_spec.js index 29da09f17..b70755a72 100644 --- a/test/nodes/core/parsers/70-CSV_spec.js +++ b/test/nodes/core/parsers/70-CSV_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var csvNode = require("../../../../nodes/core/parsers/70-CSV.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('CSV node', function() { diff --git a/test/nodes/core/parsers/70-HTML_spec.js b/test/nodes/core/parsers/70-HTML_spec.js index 50622d949..bfa0bf863 100644 --- a/test/nodes/core/parsers/70-HTML_spec.js +++ b/test/nodes/core/parsers/70-HTML_spec.js @@ -20,7 +20,7 @@ var path = require("path"); var fs = require('fs-extra'); var htmlNode = require("../../../../nodes/core/parsers/70-HTML.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('html node', function() { diff --git a/test/nodes/core/parsers/70-JSON_spec.js b/test/nodes/core/parsers/70-JSON_spec.js index 833d118e1..67c5f3daf 100644 --- a/test/nodes/core/parsers/70-JSON_spec.js +++ b/test/nodes/core/parsers/70-JSON_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var jsonNode = require("../../../../nodes/core/parsers/70-JSON.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('JSON node', function() { diff --git a/test/nodes/core/parsers/70-XML_spec.js b/test/nodes/core/parsers/70-XML_spec.js index 79c1de30f..0348130e6 100644 --- a/test/nodes/core/parsers/70-XML_spec.js +++ b/test/nodes/core/parsers/70-XML_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var xmlNode = require("../../../../nodes/core/parsers/70-XML.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('XML node', function() { diff --git a/test/nodes/core/parsers/70-YAML_spec.js b/test/nodes/core/parsers/70-YAML_spec.js index 9f2fa7ec5..aefbe48f3 100644 --- a/test/nodes/core/parsers/70-YAML_spec.js +++ b/test/nodes/core/parsers/70-YAML_spec.js @@ -16,7 +16,7 @@ var should = require("should"); var yamlNode = require("../../../../nodes/core/parsers/70-YAML.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('YAML node', function() { diff --git a/test/nodes/core/storage/28-tail_spec.js b/test/nodes/core/storage/28-tail_spec.js index d09084575..622000957 100644 --- a/test/nodes/core/storage/28-tail_spec.js +++ b/test/nodes/core/storage/28-tail_spec.js @@ -20,7 +20,7 @@ var os = require('os'); var fs = require('fs-extra'); var sinon = require('sinon'); var tailNode = require("../../../../nodes/core/storage/28-tail.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('tail Node', function() { diff --git a/test/nodes/core/storage/50-file_spec.js b/test/nodes/core/storage/50-file_spec.js index 6da2b158a..32beaefc4 100644 --- a/test/nodes/core/storage/50-file_spec.js +++ b/test/nodes/core/storage/50-file_spec.js @@ -20,7 +20,7 @@ var fs = require('fs-extra'); var os = require('os'); var sinon = require("sinon"); var fileNode = require("../../../../nodes/core/storage/50-file.js"); -var helper = require("../../helper.js"); +var helper = require("node-red-node-test-helper"); describe('file Nodes', function() { diff --git a/test/nodes/helper.js b/test/nodes/helper.js deleted file mode 100644 index decbaccb4..000000000 --- a/test/nodes/helper.js +++ /dev/null @@ -1,168 +0,0 @@ -/** - * Copyright JS Foundation and other contributors, http://js.foundation - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - **/ - -var should = require("should"); -var sinon = require("sinon"); -var when = require("when"); -var request = require('supertest'); -var express = require("express"); -var nock; -if (!process.version.match(/^v0\.[0-9]\./)) { - // only set nock for node >= 0.10 - try { - nock = require('nock'); - } catch (err) { - // nevermind, will skip nock tests - nock = null; - } -} -var RED = require("../../red/red.js"); -var redNodes = require("../../red/runtime/nodes"); -var flows = require("../../red/runtime/nodes/flows"); -var credentials = require("../../red/runtime/nodes/credentials"); -var comms = require("../../red/api/editor/comms.js"); -var log = require("../../red/runtime/log.js"); -var context = require("../../red/runtime/nodes/context.js"); -var events = require("../../red/runtime/events.js"); - -var http = require('http'); -var express = require('express'); -var app = express(); - -var address = '127.0.0.1'; -var listenPort = 0; // use ephemeral port -var port; -var url; -var logSpy; -var server; - -function helperNode(n) { - RED.nodes.createNode(this, n); -} - -module.exports = { - load: function(testNode, testFlows, testCredentials, cb) { - var i; - - logSpy = sinon.spy(log,"log"); - logSpy.FATAL = log.FATAL; - logSpy.ERROR = log.ERROR; - logSpy.WARN = log.WARN; - logSpy.INFO = log.INFO; - logSpy.DEBUG = log.DEBUG; - logSpy.TRACE = log.TRACE; - logSpy.METRIC = log.METRIC; - - if (typeof testCredentials === 'function') { - cb = testCredentials; - testCredentials = {}; - } - - var storage = { - getFlows: function() { - return when.resolve({flows:testFlows,credentials:testCredentials}); - } - }; - - var settings = { - available: function() { return false; } - }; - - var red = {}; - for (i in RED) { - if (RED.hasOwnProperty(i) && !/^(init|start|stop)$/.test(i)) { - var propDescriptor = Object.getOwnPropertyDescriptor(RED,i); - Object.defineProperty(red,i,propDescriptor); - } - } - - red["_"] = function(messageId) { - return messageId; - }; - - redNodes.init({events:events,settings:settings, storage:storage,log:log,}); - RED.nodes.registerType("helper", helperNode); - if (Array.isArray(testNode)) { - for (i = 0; i < testNode.length; i++) { - testNode[i](red); - } - } else { - testNode(red); - } - flows.load().then(function() { - flows.startFlows(); - should.deepEqual(testFlows, flows.getFlows().flows); - cb(); - }); - }, - - unload: function() { - // TODO: any other state to remove between tests? - redNodes.clearRegistry(); - logSpy.restore(); - context.clean({allNodes:[]}); - return flows.stopFlows(); - }, - - getNode: function(id) { - return flows.get(id); - }, - - credentials: credentials, - - clearFlows: function() { - return flows.stopFlows(); - }, - - request: function() { - return request(RED.httpAdmin); - }, - - startServer: function(done) { - server = http.createServer(function(req,res) { app(req,res); }); - RED.init(server, { - SKIP_BUILD_CHECK: true, - logging:{console:{level:'off'}} - }); - server.listen(listenPort, address); - server.on('listening', function() { - port = server.address().port; - url = 'http://' + address + ':' + port; - comms.start(); - done(); - }); - }, - - //TODO consider saving TCP handshake/server reinit on start/stop/start sequences - stopServer: function(done) { - if (server) { - try { - server.on('close', function() { - comms.stop(); - }); - server.close(done); - } catch(e) { - done(); - } - } - }, - - url: function() { return url; }, - - nock: nock, - - log: function() { return logSpy;} -}; From c9e2fce94d1e0cdd1208c97d1fac4fe84f25a171 Mon Sep 17 00:00:00 2001 From: KatsuyaHoshii Date: Tue, 27 Mar 2018 16:09:04 +0900 Subject: [PATCH 02/40] test for httprequest node --- package.json | 3 + test/nodes/core/io/21-httprequest_spec.js | 1502 +++++++++++++++------ 2 files changed, 1122 insertions(+), 383 deletions(-) diff --git a/package.json b/package.json index 2c816569a..8e48f074c 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "bcrypt": "~1.0.3" }, "devDependencies": { + "basic-auth": "^2.0.0", "chromedriver": "^2.33.2", "grunt": "~1.0.1", "grunt-chmod": "~1.1.1", @@ -98,6 +99,8 @@ "grunt-sass": "~2.0.0", "grunt-simple-mocha": "~0.4.1", "grunt-webdriver": "^2.0.3", + "hoek": "^4.2.1", + "http-proxy": "^1.16.2", "istanbul": "0.4.5", "mocha": "~3.4.2", "should": "^8.4.0", diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index 060e8fcad..6387d21a4 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -16,17 +16,32 @@ var when = require("when"); var http = require("http"); +var https = require("https"); var should = require("should"); var express = require("express"); var bodyParser = require('body-parser'); var helper = require("../../helper.js"); var httpRequestNode = require("../../../../nodes/core/io/21-httprequest.js"); +var tlsNode = require("../../../../nodes/core/io/05-tls.js"); var hashSum = require("hash-sum"); +var httpProxy = require('http-proxy'); +var cookieParser = require('cookie-parser'); +var RED = require("../../../../red/red.js"); +var fs = require('fs-extra'); +var hoek = require('hoek'); +var auth = require('basic-auth'); describe('HTTP Request Node', function() { var testApp; var testServer; var testPort = 9000; + var testSslServer; + var testSslPort = 9100; + var testProxyServer; + var testProxyPort = 9200; + + //save environment variables + var preEnv; function startServer(done) { testPort += 1; @@ -35,21 +50,101 @@ describe('HTTP Request Node', function() { startServer(done); }); testServer.listen(testPort,function(err) { + testSslPort += 1; + var sslOptions = { + key: fs.readFileSync('test/resources/ssl/server.key'), + cert: fs.readFileSync('test/resources/ssl/server.crt') + /* + Country Name (2 letter code) [AU]: + State or Province Name (full name) [Some-State]: + Locality Name (eg, city) []: + Organization Name (eg, company) [Internet Widgits Pty Ltd]: + Organizational Unit Name (eg, section) []: + Common Name (e.g. server FQDN or YOUR name) []:localhost + Email Address []: + + Please enter the following 'extra' attributes to be sent with your certificate request + A challenge password []: + An optional company name []: + */ + }; + testSslServer = https.createServer(sslOptions,testApp); + testSslServer.listen(testSslPort); + + testProxyPort += 1; + testProxyServer = httpProxy.createProxyServer({target:'http://localhost:' + testPort}); + testProxyServer.on('proxyReq', function(proxyReq, req, res, options) { + proxyReq.setHeader('x-testproxy-header', 'foobar'); + }); + testProxyServer.on('proxyRes', function (proxyRes, req, res, options) { + if (req.url == getTestURL('/proxyAuthenticate')){ + var user = auth.parse(req.headers['proxy-authorization']); + if (!(user.name == "foouser" && user.pass == "barpassword")){ + proxyRes.headers['proxy-authenticate'] = 'BASIC realm="test"'; + proxyRes.statusCode = 407; + } + } + }); + testProxyServer.listen(testProxyPort); done(); - }) + }); } function getTestURL(url) { return "http://localhost:"+testPort+url; } + function getSslTestURL(url) { + return "https://localhost:"+testSslPort+url; + } + + function getSslTestURLWithoutProtocol(url) { + return "localhost:"+testSslPort+url; + } + before(function(done) { + //save environment variables + preEnv = hoek.clone(process.env); + testApp = express(); testApp.use(bodyParser.raw({type:"*/*"})); + testApp.use(cookieParser()); testApp.get('/statusCode204', function(req,res) { res.status(204).end();}) testApp.get('/text', function(req, res){ res.send('hello'); }); + testApp.get('/redirectToText', function(req, res){ res.status(302).set('Location', getTestURL('/text')).end(); }); testApp.get('/json-valid', function(req, res){ res.json({a:1}); }); testApp.get('/json-invalid', function(req, res){ res.set('Content-Type', 'application/json').send("{a:1"); }); + testApp.get('/headersInspect', function(req, res){ res.set('x-test-header', 'bar').send("a"); }); + testApp.get('/timeout', function(req, res){ + setTimeout(function() { + res.send('hello'); + }, 10000); + }); + testApp.get('/checkCookie', function(req, res){ + var value = req.cookies.data; + res.send(value); + }); + testApp.get('/setCookie', function(req, res){ + res.cookie('data','hello'); + res.send(""); + }); + testApp.get('/authenticate', function(req, res){ + var user = auth.parse(req.headers['authorization']); + var result = { + user: user.name, + pass: user.pass, + } + res.json(result); + }); + testApp.get('/proxyAuthenticate', function(req, res){ + var user = auth.parse(req.headers['proxy-authorization']); + var result = { + user: user.name, + pass: user.pass, + headers: req.headers + } + res.json(result); + }); testApp.post('/postInspect', function(req,res) { var result = { body: req.body.toString(), @@ -57,413 +152,1054 @@ describe('HTTP Request Node', function() { } res.json(result); }); + testApp.put('/putInspect', function(req,res) { + var result = { + body: req.body.toString(), + headers: req.headers + } + res.json(result); + }); + testApp.delete('/deleteInspect', function(req,res) { res.status(204).end();}); + testApp.head('/headInspect', function(req,res) { res.status(204).end();}); + testApp.patch('/patchInspect', function(req,res) { + var result = { + body: req.body.toString(), + headers: req.headers + } + res.json(result); + }); + testApp.trace('/traceInspect', function(req,res) { + var result = { + body: req.body.toString(), + headers: req.headers + } + res.json(result); + }); + testApp.options('/*', function(req,res) { + res.status(200).end(); + }); startServer(function() { helper.startServer(done); }); }); after(function() { + //delete environment variables that were not set + if (!preEnv.http_proxy){ + delete process.env.http_proxy; + } + if (!preEnv.HTTP_PROXY){ + delete process.env.HTTP_PROXY; + } + if (!preEnv.no_proxy){ + delete process.env.no_proxy; + } + if (!preEnv.NO_PROXY){ + delete process.env.NO_PROXY; + } + //compare with saved environment variables + process.env.should.be.deepEqual(preEnv); + testServer.close(); + testProxyServer.close(); }); + afterEach(function() { helper.unload(); }); - it('get plain text content', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/text')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload','hello'); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-length',''+('hello'.length)); - msg.headers.should.have.property('content-type').which.startWith('text/html'); - done(); - } catch(err) { - done(err); - } + describe('request', function() { + it('should get plain text content', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should get JSON content', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/json-valid')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload',{a:1}); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should send the payload as the body of a POST as application/json', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('{"foo":"abcde"}'); + msg.payload.headers.should.have.property('content-type').which.startWith('application/json'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:{foo:"abcde"}}); + }); + }); + + it('should send a payload of 0 as the body of a POST as text/plain', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('0'); + msg.payload.headers.should.have.property('content-length','1'); + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:0, headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should send an Object payload as the body of a POST', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('{"foo":"abcde"}'); + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:{foo:"abcde"}, headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should send a Buffer as the body of a POST', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('hello'); + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:new Buffer('hello'), headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should send form-based request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.body.should.equal("foo=1%202%203&bar="); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('content-type','application/x-www-form-urlencoded'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:{foo:'1 2 3', bar:''}, headers: { 'content-type': 'application/x-www-form-urlencoded'}}); + }); + }); + + it('should send PUT request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"PUT",ret:"obj",url:getTestURL('/putInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('foo'); + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should send DELETE request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"DELETE",ret:"obj",url:getTestURL('/deleteInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload',''); + msg.should.have.property('statusCode',204); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:{foo:"abcde"}}); + }); + }); + + it('should send HEAD request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",ret:"txt",url:getTestURL('/headInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload',''); + msg.should.have.property('statusCode',204); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", method:"head"}); + }); + }); + + it('should send PATCH request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"PATCH",ret:"obj",url:getTestURL('/patchInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('foo'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('etag'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should send OPTIONS request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",ret:"obj",url:getTestURL('/*')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", method:"options"}); + }); + }); + + it('should send TRACE request', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",ret:"obj",url:getTestURL('/traceInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + msg.payload.body.should.eql('foo'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", method:"trace", headers: { 'content-type': 'text/plain'}}); + }); + }); + + it('should get Buffer content', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"bin",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload'); + Buffer.isBuffer(msg.payload).should.be.true(); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should return plain text when JSON fails to parse', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/json-invalid')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload',"{a:1"); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-type').which.startWith('application/json'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should return the status code', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/statusCode204')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload',''); + msg.should.have.property('statusCode',204); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use msg.url', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", url:"/foo"}); + }); + }); + + it('should output an error when URL is not provided', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:""}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var inError = false; + n2.on("input", function(msg) { + inError = true; + }); + n1.receive({payload:"foo"}); + setTimeout(function() { + if (inError) { + done(new Error("no url allowed though")) + } else { + done(); + } + },20) + }); + }); + + it('should allow the message to provide the url', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt"}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo",url:getTestURL('/text')}); + }); + }); + + it('should allow the url to contain mustache placeholders', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/te{{placeholder}}')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo",placeholder:"xt"}); + }); + }); + + it('should allow the url to be missing the http:// prefix', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/text').substring("http://".length)}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should reject non http:// schemes - node config', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:"ftp://foo"}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var inError = false; + n2.on("input", function(msg) { + inError = true; + }); + n1.receive({payload:"foo"}); + setTimeout(function() { + if (inError) { + done(new Error("non http(s):// scheme allowed through")) + } else { + done(); + } + },20) + }); + }); + + it('should reject non http:// schemes - msg.url', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt"}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + var inError = false; + n2.on("input", function(msg) { + inError = true; + }); + n1.receive({payload:"foo",url:"ftp://foo"}); + setTimeout(function() { + if (inError) { + done(new Error("non http(s):// scheme allowed through")) + } else { + done(); + } + },20) + }); + }); + + it('should use msg.method', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", method:"POST"}); + }); + }); + + it('should allow the message to provide the method', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",ret:"txt",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo",method:"get"}); + }); + }); + + it('should receive msg.responseUrl', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.should.have.property('responseUrl', getTestURL('/text')); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should receive msg.responseUrl when redirected', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/redirectToText')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('responseUrl', getTestURL('/text')); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('shuold output an error when request timeout occurred', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/timeout')}, + {id:"n2", type:"helper"}]; + var timeout = RED.settings.httpRequestTimeout; + RED.settings.httpRequestTimeout = 50; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode','ECONNRESET'); + done(); + } catch(err) { + done(err); + } finally { + RED.settings.httpRequestTimeout = timeout; + } + }); + n1.receive({payload:"foo"}); }); - n1.receive({payload:"foo"}); }); }); - it('get JSON content', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/json-valid')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload',{a:1}); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } + describe('HTTP header', function() { + it('should receive cookie', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/setCookie')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.responseCookies.should.have.property('data'); + msg.responseCookies.data.should.have.property('value','hello'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should send cookie with string', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','abc'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", cookies:{data:'abc'}}); + }); + }); + + it('should send cookie with obejct data', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','abc'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", cookies:{data:{value:'abc'}}}); + }); + }); + + it('should send cookie by msg.headers', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','abc'); + msg.should.have.property('statusCode',200); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", cookies:{boo:'123'}, headers:{'cookie':'data=abc'}}); + }); + }); + + it('should convert all HTTP headers into lower case', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.payload.headers.should.have.property('content-length', "3"); + msg.payload.headers.should.have.property('if-modified-since','Sun, 01 Jun 2000 00:00:00 GMT'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", headers: { 'Content-Type':'text/plain', 'Content-Length': "3", 'If-Modified-Since':'Sun, 01 Jun 2000 00:00:00 GMT'}}); + }); + }); + + it('should receive HTTP header', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/headersInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.headers.should.have.property('x-test-header','bar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should ignore unmodified x-node-red-request-node header', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.payload.headers.should.have.property('content-type').which.startWith('application/json'); + msg.payload.headers.should.not.have.property('x-node-red-request-node'); + done(); + } catch(err) { + done(err); + } + }); + // Pass in a headers property with an unmodified x-node-red-request-node hash + // This should cause the node to ignore the headers + n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"67690139"}}); + }); + }); + + it('should use modified msg.headers property', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + try { + msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); + msg.payload.headers.should.not.have.property('x-node-red-request-node'); + done(); + } catch(err) { + done(err); + } + }); + // Pass in a headers property with a x-node-red-request-node hash that doesn't match the contents + // This should cause the node to use the headers + n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"INVALID_SUM"}}); }); - n1.receive({payload:"foo"}); }); }); - it('get Buffer content', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"bin",url:getTestURL('/text')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - Buffer.isBuffer(msg.payload).should.be.true(); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type'); - done(); - } catch(err) { - done(err); - } + describe('protocol', function() { + it('should use msg.rejectUnauthorized', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getSslTestURL('/text')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n2 = helper.getNode("n2"); + var n1 = helper.getNode("n1"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + msg.should.have.property('responseUrl').which.startWith('https://'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo", rejectUnauthorized: false}); + }); + }); + + it('should use tls-config', function(done) { + var flow = [ + {id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getSslTestURLWithoutProtocol('/text'),tls:"n3"}, + {id:"n2", type:"helper"}, + {id:"n3", type:"tls-config", cert:"test/resources/ssl/server.crt", key:"test/resources/ssl/server.key", ca:""}]; + var testNodes = [httpRequestNode, tlsNode]; + helper.load(testNodes, flow, function() { + var n3 = helper.getNode("n3"); + var n2 = helper.getNode("n2"); + var n1 = helper.getNode("n1"); + n2.on("input", function(msg) { + try { + msg.should.have.property('payload','hello'); + msg.should.have.property('statusCode',200); + msg.should.have.property('headers'); + msg.headers.should.have.property('content-length',''+('hello'.length)); + msg.headers.should.have.property('content-type').which.startWith('text/html'); + msg.should.have.property('responseUrl').which.startWith('https://'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use http_proxy', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.http_proxy; + process.env.http_proxy = "http://localhost:" + testProxyPort; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.http_proxy = preHttpProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use http_proxy when environment variable is invalid', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.http_proxy; + process.env.http_proxy = "invalidvalue"; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.http_proxy = preHttpProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use HTTP_PROXY', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.HTTP_PROXY; + process.env.HTTP_PROXY = "http://localhost:" + testProxyPort; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.HTTP_PROXY = preHttpProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use no_proxy', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.http_proxy; + var preNoProxy = process.env.no_proxy; + process.env.http_proxy = "http://localhost:" + testProxyPort; + process.env.no_proxy = "foo,localhost"; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.http_proxy = preHttpProxy; + process.env.no_proxy = preNoProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should use NO_PROXY', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.HTTP_PROXY; + var preNoProxy = process.env.NO_PROXY; + process.env.HTTP_PROXY = "http://localhost:" + testProxyPort; + process.env.NO_PROXY = "foo,localhost"; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.HTTP_PROXY = preHttpProxy; + process.env.NO_PROXY = preNoProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); }); - n1.receive({payload:"foo"}); }); }); - it('returns plain text when JSON fails to parse', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/json-invalid')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload',"{a:1"); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } + describe('authentication', function() { + it('should authenticate on server', function(done) { + var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/authenticate')}, + {id:"n2", type:"helper"}]; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n1.credentials = {user:'userfoo', password:'passwordfoo'} + n2.on("input", function(msg) { + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('user', 'userfoo'); + msg.payload.should.have.property('pass', 'passwordfoo'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }) + + it('should authenticate on proxy server', function(done) { + var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.http_proxy; + process.env.http_proxy = "http://foouser:barpassword@localhost:" + testProxyPort; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.http_proxy = preHttpProxy; + try { + msg.should.have.property('statusCode',200); + msg.payload.should.have.property('user', 'foouser'); + msg.payload.should.have.property('pass', 'barpassword'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); + }); + }); + + it('should output an error when proxy authentication was failed', function(done) { + var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')}, + {id:"n2", type:"helper"}]; + var preHttpProxy = process.env.http_proxy; + process.env.http_proxy = "http://xxxuser:barpassword@localhost:" + testProxyPort; + helper.load(httpRequestNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + process.env.http_proxy = preHttpProxy; + try { + msg.should.have.property('statusCode',407); + msg.headers.should.have.property('proxy-authenticate', 'BASIC realm="test"'); + msg.payload.should.have.property('headers'); + msg.payload.headers.should.have.property('x-testproxy-header','foobar'); + done(); + } catch(err) { + done(err); + } + }); + n1.receive({payload:"foo"}); }); - n1.receive({payload:"foo"}); }); }); - - - it('return the status code', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/statusCode204')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload',''); - msg.should.have.property('statusCode',204); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:"foo"}); - }); - }); - - it('allow the url to be missing the http:// prefix', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/text').substring("http://".length)}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload','hello'); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-length',''+('hello'.length)); - msg.headers.should.have.property('content-type').which.startWith('text/html'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:"foo"}); - }); - }); - - it('reject non http:// schemes - node config', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:"ftp://foo"}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var inError = false; - n2.on("input", function(msg) { - inError = true; - }); - n1.receive({payload:"foo"}); - setTimeout(function() { - if (inError) { - done(new Error("non http(s):// scheme allowed through")) - } else { - done(); - } - },20) - }); - }); - - it('reject non http:// schemes - msg.url', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt"}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - var inError = false; - n2.on("input", function(msg) { - inError = true; - }); - n1.receive({payload:"foo",url:"ftp://foo"}); - setTimeout(function() { - if (inError) { - done(new Error("non http(s):// scheme allowed through")) - } else { - done(); - } - },20) - }); - }); - it('allow the message to provide the url', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt"}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload','hello'); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-length',''+('hello'.length)); - msg.headers.should.have.property('content-type').which.startWith('text/html'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:"foo",url:getTestURL('/text')}); - }); - }); - - it('allow the message to provide the method', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"use",ret:"txt",url:getTestURL('/text')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload','hello'); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-length',''+('hello'.length)); - msg.headers.should.have.property('content-type').which.startWith('text/html'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:"foo",method:"get"}); - }); - }); - - it('allow the url to contain mustache placeholders', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getTestURL('/te{{placeholder}}')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload','hello'); - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-length',''+('hello'.length)); - msg.headers.should.have.property('content-type').which.startWith('text/html'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:"foo",placeholder:"xt"}); - }); - }); - - it('send the payload as the body of a POST as application/json', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - msg.payload.body.should.eql('{"foo":"abcde"}'); - msg.payload.headers.should.have.property('content-type').which.startWith('application/json'); - - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:{foo:"abcde"}}); - }); - }); - - it('send a payload of 0 as the body of a POST as text/plain', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - msg.payload.body.should.eql('0'); - msg.payload.headers.should.have.property('content-length','1'); - msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); - - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:0, headers: { 'content-type': 'text/plain'}}); - }); - }); - - it('send an Object payload as the body of a POST', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - msg.payload.body.should.eql('{"foo":"abcde"}'); - msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); - - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:{foo:"abcde"}, headers: { 'content-type': 'text/plain'}}); - }); - }) - - it('send a Buffer as the body of a POST', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - msg.payload.body.should.eql('hello'); - msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); - - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:new Buffer('hello'), headers: { 'content-type': 'text/plain'}}); - }); - }) - - it('send a Buffer as the body of a POST', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.should.have.property('payload'); - msg.payload.body.should.eql('hello'); - msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); - - msg.should.have.property('statusCode',200); - msg.should.have.property('headers'); - msg.headers.should.have.property('content-type').which.startWith('application/json'); - done(); - } catch(err) { - done(err); - } - }); - n1.receive({payload:new Buffer('hello'), headers: { 'content-type': 'text/plain'}}); - }); - }) - - it('ignores unmodified msg.headers property', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.payload.headers.should.have.property('content-type').which.startWith('application/json'); - msg.payload.headers.should.not.have.property('x-node-red-request-node'); - done(); - } catch(err) { - done(err); - } - }); - // Pass in a headers property with an unmodified x-node-red-request-node hash - // This should cause the node to ignore the headers - n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"67690139"}}); - }); - }) - - it('uses modified msg.headers property', function(done) { - var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, - {id:"n2", type:"helper"}]; - helper.load(httpRequestNode, flow, function() { - var n1 = helper.getNode("n1"); - var n2 = helper.getNode("n2"); - n2.on("input", function(msg) { - try { - msg.payload.headers.should.have.property('content-type').which.startWith('text/plain'); - msg.payload.headers.should.not.have.property('x-node-red-request-node'); - done(); - } catch(err) { - done(err); - } - }); - // Pass in a headers property with a x-node-red-request-node hash that doesn't match the contents - // This should cause the node to use the headers - n1.receive({payload:{foo:"bar"}, headers: { 'content-type': 'text/plain', "x-node-red-request-node":"INVALID_SUM"}}); - }); - }) - }); From d2aa3d1868af2f7449b7454e190a0bf103000490 Mon Sep 17 00:00:00 2001 From: KatsuyaHoshii Date: Tue, 27 Mar 2018 17:07:29 +0900 Subject: [PATCH 03/40] Add SSL server certificate --- test/resources/ssl/server.crt | 20 ++++++++++++++++++++ test/resources/ssl/server.key | 27 +++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 test/resources/ssl/server.crt create mode 100644 test/resources/ssl/server.key diff --git a/test/resources/ssl/server.crt b/test/resources/ssl/server.crt new file mode 100644 index 000000000..493afc252 --- /dev/null +++ b/test/resources/ssl/server.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDMDCCAhgCCQDPGPyu5M6ZaDANBgkqhkiG9w0BAQsFADBZMQswCQYDVQQGEwJB +VTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0 +cyBQdHkgTHRkMRIwEAYDVQQDDAlsb2NhbGhvc3QwIBcNMTgwMzE2MDY0ODU1WhgP +MjExODAyMjAwNjQ4NTVaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0 +YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMM +CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMifGelM +k/b3HeIj98y9P5jS+Qblqpq7+gsCaL+qglMFmG0QXe6Ordkrh3xeY0uTkaFatwLM +WMzoX60nNdaVjC9U9RlQLK/3nncCveexxRUGtI8VpxN04ivBE/ULhtJeStQFrfyt +LWr1WWf8o8P/EWzZnh0Y1oHc0XqhOPHu9Nfd9kn5nfHNd/xbY8KXa4DkVSJ1lLFK +3t/nSWttchF8zKgNpoQznNGqUTjT28l0sS8fyH76DyRj3Ke6xdNxX2NRUU0PnGFI +RMsBG4Qrzo5xY7lQP7uVVgZUlxryw+NuZuC1PBXaUKJOf6CGwrTq5WB9zF1iBZCs +wD68NvtLd0kHEgECAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAfqNOg2v90r5x4lFo +SYmPUoX24gdwHd/mfCzDJksB8n98X1eULYZqqRF2Q7LMkYu/twxfR3EKQX1HZxQY +LpGUYX4ubJdVTy13opJs8B4NkhvRuOAP0+b7RVt4RfuxLX9tYOB98tEbf7Mj0ccq +F4sHi+PMCh64K7rNWECHar0F51yNtNXcxJPMuHZVmj0/U7h6ZxNf+GzdTi8YKmVy +5OHI7xol/II/v3QOi1L+BaEIUkqYODKuQouJVIzu4zX6JRfAaxwjJmliYoJm7OEY +dFMEQUw1Ggsos+KbkGi9mCDbveYpWcZTR8nfPwmx+oJtt47DTHUC3KSdRxgtfjGs +otmVSw== +-----END CERTIFICATE----- diff --git a/test/resources/ssl/server.key b/test/resources/ssl/server.key new file mode 100644 index 000000000..4b8d6a1a5 --- /dev/null +++ b/test/resources/ssl/server.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAyJ8Z6UyT9vcd4iP3zL0/mNL5BuWqmrv6CwJov6qCUwWYbRBd +7o6t2SuHfF5jS5ORoVq3AsxYzOhfrSc11pWML1T1GVAsr/eedwK957HFFQa0jxWn +E3TiK8ET9QuG0l5K1AWt/K0tavVZZ/yjw/8RbNmeHRjWgdzReqE48e701932Sfmd +8c13/FtjwpdrgORVInWUsUre3+dJa21yEXzMqA2mhDOc0apRONPbyXSxLx/IfvoP +JGPcp7rF03FfY1FRTQ+cYUhEywEbhCvOjnFjuVA/u5VWBlSXGvLD425m4LU8FdpQ +ok5/oIbCtOrlYH3MXWIFkKzAPrw2+0t3SQcSAQIDAQABAoIBAGmbryUrpZxU24tG +idRiLw9Ax8yEq7lGiMqw2vlCRdZ0VJfdDMVeoE945ZpniXeoV/oLadl0Pq6nCG56 +/JFYKfJkk51eoheDjwxxCgzkfK2j2PqVWF0ao1CLE/ljtvYYouVXlA42D3mFbCoc +SQ0MwVx+dgg1If48gp0+L17T/ll/VOOQumts5UzoKC8YABLL00g5ZL9/jZlVipgl +HfENMPWOfy3q5kSgQqvTWTMdSE6644ryV890mrwcC/RzqQBSNgRh1Lqx3jcXQSdN +x5C19gEK60hZqcvzBkKYudMHUC6I0lcuao1xwBnHUQIVKmLFPZBUIQq3tVar/YUc +d65cJpECgYEA5D9QilQpHxv875wBvBOEbyt9TqDBBN/5JpGQ9sBKpA0eNp3UzrOr ++n0TlyoDZYjkxgNJScS4TpeKde1Hk5j2kkMngjS69dn4G6wmOI79gAOGrCiJd1/I +AWb09KxUKlWBbfKuLHdl1wSMCYQornDdXxYCxhv9sMZKbEJ//tsI420CgYEA4QPf +n/dRAm+6zwNQTWOYWlj5jsG1TilBBCtoRqUqVlrAgR6rS1lgOleHkVrWH0g0Lkmh +9DxWiWuNNXxdU/5zx9AQn/JuHuL8EjDLN5r7idcg2LtEElCkr12y0I9nzS2OOZnj +MTioIh+hghzNuk09NlVJrHi48bJUVL/6Ws7ruGUCgYBT9UJAD+MscVQiI2Wz9A30 +ArBOOu2lSGnSmRsU2PjbzYN+naIJAqhRNK7/HNIxCCD3AYB05SrSpgWliUmZ7ltM +w+0FhTX8d1g/fZx1k4uGCkYAj8y5H39nnKKgWb9/7wH0Gp+c9bJ9XEvSuE1qlVOo +xWTx0JwJ6Xa4yeFhMtrbJQKBgF/FfErjwvEciRBPQsCNoWzi7eUbAYYw/OE/cHSR +HAIBQmoymYnKkrCCTMtLNFPAMaV55ZrEi7iVtFaNhlOXu8PSBSFu1/wBdHRxnC0g +o+s5S1uz6Pc6p72UTeWDBBVKTHyryQ1MJhPQDrgIdm/TLDiR+HeWMnF9C3O++lno +NGAZAoGBAKhsmatxVD9B3jvUDd/CWhXVDSZQECrfJ+Uy1q6b5NO5yMibpIZv14Nj +VT+b2qXoO1wL6htTRJXXNPmrB/JtrLiLg/vxVuA7CPSgot8SDA+/lbRhf1n/SKnD +ECXrEUmq28SgBItbY4vcy5PVEHRvlzqO/LpD6Y7iGNpR7zw9Yk3b +-----END RSA PRIVATE KEY----- From 20f03c356c099ef5fb6494eb5b734221383ca0ce Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Tue, 17 Apr 2018 10:52:53 +0200 Subject: [PATCH 04/40] better script error handling. Also pass the error in msg so that it can handled later --- nodes/core/core/80-function.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/nodes/core/core/80-function.js b/nodes/core/core/80-function.js index 69d6d0ca2..7f0c2627b 100644 --- a/nodes/core/core/80-function.js +++ b/nodes/core/core/80-function.js @@ -204,7 +204,12 @@ module.exports = function(RED) { } var context = vm.createContext(sandbox); try { - this.script = vm.createScript(functionText); + this.script = vm.createScript(functionText, { + filename: this.name + '_' + this.id, // filename for stack traces + lineOffset: -11, // line number offset to be used for stack traces + columnOffset: 0, // column number offset to be used for stack traces + displayErrors: true + }); this.on("input", function(msg) { try { var start = process.hrtime(); @@ -219,7 +224,7 @@ module.exports = function(RED) { this.status({fill:"yellow",shape:"dot",text:""+converted}); } } catch(err) { - + msg.error = err; var line = 0; var errorMessage; var stack = err.stack.split(/\r?\n/); From 5967f4b0d44d3b973a8a8c9d335c083381ff0d03 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Tue, 17 Apr 2018 15:46:09 +0200 Subject: [PATCH 05/40] fix error stack --- nodes/core/core/80-function.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/nodes/core/core/80-function.js b/nodes/core/core/80-function.js index 7f0c2627b..2d5f7901a 100644 --- a/nodes/core/core/80-function.js +++ b/nodes/core/core/80-function.js @@ -224,7 +224,15 @@ module.exports = function(RED) { this.status({fill:"yellow",shape:"dot",text:""+converted}); } } catch(err) { + //remove unwanted part + const index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/); + err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n'); + console.log(index, err.stack); + const stack = err.stack.split(/\r?\n/); + + //store the error in msg to be used in flows msg.error = err; + var line = 0; var errorMessage; var stack = err.stack.split(/\r?\n/); From ff355af9f2d59337552dfd54b34ea12c68d10f06 Mon Sep 17 00:00:00 2001 From: mblackstock Date: Tue, 17 Apr 2018 11:59:47 -0700 Subject: [PATCH 06/40] use newer test helper --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 307f147b3..21eae85c7 100644 --- a/package.json +++ b/package.json @@ -107,7 +107,7 @@ "wdio-mocha-framework": "^0.5.11", "wdio-spec-reporter": "^0.1.3", "webdriverio": "^4.9.11", - "node-red-node-test-helper": "0.1.5" + "node-red-node-test-helper": "0.1.6" }, "engines": { "node": ">=4" From 6cd9ccc37c4194f655494e5fc0ad1c7eef4dbc74 Mon Sep 17 00:00:00 2001 From: KatsuyaHoshii Date: Mon, 23 Apr 2018 14:31:37 +0900 Subject: [PATCH 07/40] Refactor test cases --- package.json | 1 - test/nodes/core/io/21-httprequest_spec.js | 123 ++++++++++++---------- 2 files changed, 67 insertions(+), 57 deletions(-) diff --git a/package.json b/package.json index 8e48f074c..f954e1bc6 100644 --- a/package.json +++ b/package.json @@ -99,7 +99,6 @@ "grunt-sass": "~2.0.0", "grunt-simple-mocha": "~0.4.1", "grunt-webdriver": "^2.0.3", - "hoek": "^4.2.1", "http-proxy": "^1.16.2", "istanbul": "0.4.5", "mocha": "~3.4.2", diff --git a/test/nodes/core/io/21-httprequest_spec.js b/test/nodes/core/io/21-httprequest_spec.js index 6387d21a4..0645f6f42 100644 --- a/test/nodes/core/io/21-httprequest_spec.js +++ b/test/nodes/core/io/21-httprequest_spec.js @@ -28,7 +28,6 @@ var httpProxy = require('http-proxy'); var cookieParser = require('cookie-parser'); var RED = require("../../../../red/red.js"); var fs = require('fs-extra'); -var hoek = require('hoek'); var auth = require('basic-auth'); describe('HTTP Request Node', function() { @@ -41,7 +40,10 @@ describe('HTTP Request Node', function() { var testProxyPort = 9200; //save environment variables - var preEnv; + var preEnvHttpProxyLowerCase; + var preEnvHttpProxyUpperCase; + var preEnvNoProxyLowerCase; + var preEnvNoProxyUpperCase; function startServer(done) { testPort += 1; @@ -102,14 +104,43 @@ describe('HTTP Request Node', function() { return "localhost:"+testSslPort+url; } - before(function(done) { - //save environment variables - preEnv = hoek.clone(process.env); + function saveProxySetting() { + preEnvHttpProxyLowerCase = process.env.http_proxy; + preEnvHttpProxyUpperCase = process.env.HTTP_PROXY; + preEnvNoProxyLowerCase = process.env.no_proxy; + preEnvNoProxyUpperCase = process.env.NO_PROXY; + delete process.env.http_proxy; + delete process.env.HTTP_PROXY; + delete process.env.no_proxy; + delete process.env.NO_PROXY; + } + function restoreProxySetting() { + process.env.http_proxy = preEnvHttpProxyLowerCase; + process.env.HTTP_PROXY = preEnvHttpProxyUpperCase; + // On Windows, if environment variable of NO_PROXY that includes lower cases + // such as No_Proxy is replaced with NO_PROXY. + process.env.no_proxy = preEnvNoProxyLowerCase; + process.env.NO_PROXY = preEnvNoProxyUpperCase; + if (preEnvHttpProxyLowerCase == undefined){ + delete process.env.http_proxy; + } + if (preEnvHttpProxyUpperCase == undefined){ + delete process.env.HTTP_PROXY; + } + if (preEnvNoProxyLowerCase == undefined){ + delete process.env.no_proxy; + } + if (preEnvNoProxyUpperCase == undefined){ + delete process.env.NO_PROXY; + } + } + + before(function(done) { testApp = express(); testApp.use(bodyParser.raw({type:"*/*"})); testApp.use(cookieParser()); - testApp.get('/statusCode204', function(req,res) { res.status(204).end();}) + testApp.get('/statusCode204', function(req,res) { res.status(204).end();}); testApp.get('/text', function(req, res){ res.send('hello'); }); testApp.get('/redirectToText', function(req, res){ res.status(302).set('Location', getTestURL('/text')).end(); }); testApp.get('/json-valid', function(req, res){ res.json({a:1}); }); @@ -133,7 +164,7 @@ describe('HTTP Request Node', function() { var result = { user: user.name, pass: user.pass, - } + }; res.json(result); }); testApp.get('/proxyAuthenticate', function(req, res){ @@ -142,21 +173,21 @@ describe('HTTP Request Node', function() { user: user.name, pass: user.pass, headers: req.headers - } + }; res.json(result); }); testApp.post('/postInspect', function(req,res) { var result = { body: req.body.toString(), headers: req.headers - } + }; res.json(result); }); testApp.put('/putInspect', function(req,res) { var result = { body: req.body.toString(), headers: req.headers - } + }; res.json(result); }); testApp.delete('/deleteInspect', function(req,res) { res.status(204).end();}); @@ -165,14 +196,14 @@ describe('HTTP Request Node', function() { var result = { body: req.body.toString(), headers: req.headers - } + }; res.json(result); }); testApp.trace('/traceInspect', function(req,res) { var result = { body: req.body.toString(), headers: req.headers - } + }; res.json(result); }); testApp.options('/*', function(req,res) { @@ -184,22 +215,6 @@ describe('HTTP Request Node', function() { }); after(function() { - //delete environment variables that were not set - if (!preEnv.http_proxy){ - delete process.env.http_proxy; - } - if (!preEnv.HTTP_PROXY){ - delete process.env.HTTP_PROXY; - } - if (!preEnv.no_proxy){ - delete process.env.no_proxy; - } - if (!preEnv.NO_PROXY){ - delete process.env.NO_PROXY; - } - //compare with saved environment variables - process.env.should.be.deepEqual(preEnv); - testServer.close(); testProxyServer.close(); }); @@ -579,11 +594,11 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); setTimeout(function() { if (inError) { - done(new Error("no url allowed though")) + done(new Error("no url allowed though")); } else { done(); } - },20) + },20); }); }); @@ -666,11 +681,11 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo"}); setTimeout(function() { if (inError) { - done(new Error("non http(s):// scheme allowed through")) + done(new Error("non http(s):// scheme allowed through")); } else { done(); } - },20) + },20); }); }); @@ -687,11 +702,11 @@ describe('HTTP Request Node', function() { n1.receive({payload:"foo",url:"ftp://foo"}); setTimeout(function() { if (inError) { - done(new Error("non http(s):// scheme allowed through")) + done(new Error("non http(s):// scheme allowed through")); } else { done(); } - },20) + },20); }); }); @@ -840,7 +855,7 @@ describe('HTTP Request Node', function() { it('should send cookie with obejct data', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/checkCookie')}, - {id:"n2", type:"helper"}]; + {id:"n2", type:"helper"}]; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); @@ -988,7 +1003,7 @@ describe('HTTP Request Node', function() { var flow = [ {id:"n1",type:"http request",wires:[["n2"]],method:"GET",ret:"txt",url:getSslTestURLWithoutProtocol('/text'),tls:"n3"}, {id:"n2", type:"helper"}, - {id:"n3", type:"tls-config", cert:"test/resources/ssl/server.crt", key:"test/resources/ssl/server.key", ca:""}]; + {id:"n3", type:"tls-config", cert:"test/resources/ssl/server.crt", key:"test/resources/ssl/server.key", ca:"", verifyservercert:false}]; var testNodes = [httpRequestNode, tlsNode]; helper.load(testNodes, flow, function() { var n3 = helper.getNode("n3"); @@ -1014,13 +1029,13 @@ describe('HTTP Request Node', function() { it('should use http_proxy', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.http_proxy; + saveProxySetting(); process.env.http_proxy = "http://localhost:" + testProxyPort; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.http_proxy = preHttpProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.should.have.property('headers'); @@ -1037,13 +1052,13 @@ describe('HTTP Request Node', function() { it('should use http_proxy when environment variable is invalid', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.http_proxy; + saveProxySetting(); process.env.http_proxy = "invalidvalue"; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.http_proxy = preHttpProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.should.have.property('headers'); @@ -1060,13 +1075,13 @@ describe('HTTP Request Node', function() { it('should use HTTP_PROXY', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.HTTP_PROXY; + saveProxySetting(); process.env.HTTP_PROXY = "http://localhost:" + testProxyPort; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.HTTP_PROXY = preHttpProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.should.have.property('headers'); @@ -1083,16 +1098,14 @@ describe('HTTP Request Node', function() { it('should use no_proxy', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.http_proxy; - var preNoProxy = process.env.no_proxy; + saveProxySetting(); process.env.http_proxy = "http://localhost:" + testProxyPort; process.env.no_proxy = "foo,localhost"; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.http_proxy = preHttpProxy; - process.env.no_proxy = preNoProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); @@ -1108,16 +1121,14 @@ describe('HTTP Request Node', function() { it('should use NO_PROXY', function(done) { var flow = [{id:"n1",type:"http request",wires:[["n2"]],method:"POST",ret:"obj",url:getTestURL('/postInspect')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.HTTP_PROXY; - var preNoProxy = process.env.NO_PROXY; + saveProxySetting(); process.env.HTTP_PROXY = "http://localhost:" + testProxyPort; process.env.NO_PROXY = "foo,localhost"; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.HTTP_PROXY = preHttpProxy; - process.env.NO_PROXY = preNoProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.headers.should.not.have.property('x-testproxy-header','foobar'); @@ -1138,7 +1149,7 @@ describe('HTTP Request Node', function() { helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); - n1.credentials = {user:'userfoo', password:'passwordfoo'} + n1.credentials = {user:'userfoo', password:'passwordfoo'}; n2.on("input", function(msg) { try { msg.should.have.property('statusCode',200); @@ -1151,18 +1162,18 @@ describe('HTTP Request Node', function() { }); n1.receive({payload:"foo"}); }); - }) + }); it('should authenticate on proxy server', function(done) { var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.http_proxy; + saveProxySetting(); process.env.http_proxy = "http://foouser:barpassword@localhost:" + testProxyPort; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.http_proxy = preHttpProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',200); msg.payload.should.have.property('user', 'foouser'); @@ -1181,13 +1192,13 @@ describe('HTTP Request Node', function() { it('should output an error when proxy authentication was failed', function(done) { var flow = [{id:"n1",type:"http request", wires:[["n2"]],method:"GET",ret:"obj",url:getTestURL('/proxyAuthenticate')}, {id:"n2", type:"helper"}]; - var preHttpProxy = process.env.http_proxy; + saveProxySetting(); process.env.http_proxy = "http://xxxuser:barpassword@localhost:" + testProxyPort; helper.load(httpRequestNode, flow, function() { var n1 = helper.getNode("n1"); var n2 = helper.getNode("n2"); n2.on("input", function(msg) { - process.env.http_proxy = preHttpProxy; + restoreProxySetting(); try { msg.should.have.property('statusCode',407); msg.headers.should.have.property('proxy-authenticate', 'BASIC realm="test"'); From 3190de873e6f6719154c4047c307af33524685c1 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 1 May 2018 12:42:27 +0100 Subject: [PATCH 08/40] add output property select to HTML parse node (#1701) --- nodes/core/locales/en-US/messages.json | 3 ++- nodes/core/parsers/70-HTML.html | 8 +++++++- nodes/core/parsers/70-HTML.js | 5 +++-- test/nodes/core/parsers/70-HTML_spec.js | 22 ++++++++++++++++++++-- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 3232069db..9fc8f729b 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -671,7 +671,8 @@ "html": { "label": { "select": "Selector", - "output": "Output" + "output": "Output", + "in": "in" }, "output": { "html": "the html content of the elements", diff --git a/nodes/core/parsers/70-HTML.html b/nodes/core/parsers/70-HTML.html index 79003f935..4fa9227d1 100644 --- a/nodes/core/parsers/70-HTML.html +++ b/nodes/core/parsers/70-HTML.html @@ -2,7 +2,7 @@ diff --git a/nodes/core/parsers/70-HTML.js b/nodes/core/parsers/70-HTML.js index 7e4f48fd0..0a24be368 100644 --- a/nodes/core/parsers/70-HTML.js +++ b/nodes/core/parsers/70-HTML.js @@ -21,6 +21,7 @@ module.exports = function(RED) { function CheerioNode(n) { RED.nodes.createNode(this,n); this.property = n.property||"payload"; + this.outproperty = n.outproperty||this.property||"payload"; this.tag = n.tag; this.ret = n.ret || "html"; this.as = n.as || "single"; @@ -48,7 +49,7 @@ module.exports = function(RED) { /* istanbul ignore else */ if (pay2) { var new_msg = RED.util.cloneMessage(msg); - RED.util.setMessageProperty(new_msg,node.property,pay2); + RED.util.setMessageProperty(new_msg,node.outproperty,pay2); new_msg.parts = { id: msg._msgid, index: index, @@ -68,7 +69,7 @@ module.exports = function(RED) { index++; }); if (node.as === "single") { // Always return an array - even if blank - RED.util.setMessageProperty(msg,node.property,pay); + RED.util.setMessageProperty(msg,node.outproperty,pay); node.send(msg); } } diff --git a/test/nodes/core/parsers/70-HTML_spec.js b/test/nodes/core/parsers/70-HTML_spec.js index 5874ed3ae..a1e363761 100644 --- a/test/nodes/core/parsers/70-HTML_spec.js +++ b/test/nodes/core/parsers/70-HTML_spec.js @@ -69,7 +69,7 @@ describe('html node', function() { }); }); - it('should retrieve header contents if asked to by msg.select - alternative property', function(done) { + it('should retrieve header contents if asked to by msg.select - alternative in property', function(done) { fs.readFile(file, 'utf8', function(err, data) { var flow = [{id:"n1",type:"html",property:"foo",wires:[["n2"]],func:"return msg;"}, {id:"n2", type:"helper"}]; @@ -79,7 +79,7 @@ describe('html node', function() { var n2 = helper.getNode("n2"); n2.on("input", function(msg) { msg.should.have.property('topic', 'bar'); - should.equal(msg.foo, 'This is a test page for node 70-HTML'); + msg.foo[0].should.equal('This is a test page for node 70-HTML'); done(); }); n1.receive({foo:data,topic:"bar",select:"h1"}); @@ -87,6 +87,24 @@ describe('html node', function() { }); }); + it('should retrieve header contents if asked to by msg.select - alternative in and out properties', function(done) { + fs.readFile(file, 'utf8', function(err, data) { + var flow = [{id:"n1",type:"html",property:"foo",outproperty:"bar",tag:"h1",wires:[["n2"]],func:"return msg;"}, + {id:"n2", type:"helper"}]; + + helper.load(htmlNode, flow, function() { + var n1 = helper.getNode("n1"); + var n2 = helper.getNode("n2"); + n2.on("input", function(msg) { + msg.should.have.property('topic', 'bar'); + msg.bar[0].should.equal('This is a test page for node 70-HTML'); + done(); + }); + n1.receive({foo:data,topic:"bar"}); + }); + }); + }); + it('should emit an empty array if no matching elements', function(done) { fs.readFile(file, 'utf8', function(err, data) { var flow = [{id:"n1",type:"html",wires:[["n2"]],func:"return msg;"}, From e691351976b8a38064d630c7098264428895621b Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 1 May 2018 12:43:10 +0100 Subject: [PATCH 09/40] update settings comments to describe how to setup for ipv6 (#1675) * change default server bind to support ipv6 and ipv4 to close #1674 * Add comment re ipv6 so folk know it's capable * slightly more words re ipv6 config * Leave defaults as ipv4 but add doc to settings --- red.js | 4 ++-- settings.js | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/red.js b/red.js index 83589edd5..c7dc8b131 100755 --- a/red.js +++ b/red.js @@ -192,7 +192,7 @@ try { if (err.code == "unsupported_version") { console.log("Unsupported version of node.js:",process.version); console.log("Node-RED requires node.js v4 or later"); - } else if (err.code == "not_built") { + } else if (err.code == "not_built") { console.log("Node-RED has not been built. See README.md for details"); } else { console.log("Failed to start server:"); @@ -276,7 +276,7 @@ function getListenPath() { } var listenPath = 'http'+(settings.https?'s':'')+'://'+ - (settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost)+ + (settings.uiHost == '::'?'localhost':(settings.uiHost == '0.0.0.0'?'127.0.0.1':settings.uiHost))+ ':'+port; if (settings.httpAdminRoot !== false) { listenPath += settings.httpAdminRoot; diff --git a/settings.js b/settings.js index c3bd898ec..0438dc5ea 100644 --- a/settings.js +++ b/settings.js @@ -23,6 +23,7 @@ module.exports = { uiPort: process.env.PORT || 1880, // By default, the Node-RED UI accepts connections on all IPv4 interfaces. + // To listen on all IPv6 addresses, set uiHost to "::", // The following property can be used to listen on a specific interface. For // example, the following would only allow connections from the local machine. //uiHost: "127.0.0.1", From 94cb03f4b53e59fc5967a9beb076b438988e5064 Mon Sep 17 00:00:00 2001 From: Dave Conway-Jones Date: Tue, 1 May 2018 12:43:51 +0100 Subject: [PATCH 10/40] bind to correct port when doing udp broadcast/multicast (#1686) * bind to correct port when doing broadcast/multicast to allow better re-use of ports. * allow udp multicast to work out if ip address makes life easier for mortals * udp also handle bind to ipv6 multicast if tidy prompts to suit new function * udp node, add face to debug log for multicast if known --- nodes/core/io/32-udp.html | 2 +- nodes/core/io/32-udp.js | 113 ++++++++++++++++++------- nodes/core/locales/en-US/messages.json | 14 +-- nodes/core/locales/ja/messages.json | 3 +- nodes/core/locales/zh-CN/messages.json | 3 +- 5 files changed, 94 insertions(+), 41 deletions(-) diff --git a/nodes/core/io/32-udp.html b/nodes/core/io/32-udp.html index 3711978c4..126f1da6f 100644 --- a/nodes/core/io/32-udp.html +++ b/nodes/core/io/32-udp.html @@ -29,7 +29,7 @@
- +
diff --git a/nodes/core/io/32-udp.js b/nodes/core/io/32-udp.js index fac5135cf..096de479c 100644 --- a/nodes/core/io/32-udp.js +++ b/nodes/core/io/32-udp.js @@ -16,6 +16,7 @@ module.exports = function(RED) { "use strict"; + var os = require('os'); var dgram = require('dgram'); var udpInputPortsInUse = {}; @@ -30,6 +31,29 @@ module.exports = function(RED) { this.ipv = n.ipv || "udp4"; var node = this; + if (node.iface && node.iface.indexOf(".") === -1) { + try { + if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) { + if (node.ipv === "udp4") { + node.iface = (os.networkInterfaces())[node.iface][1].address; + } else { + node.iface = (os.networkInterfaces())[node.iface][0].address; + } + } + else { + if (node.ipv === "udp4") { + node.iface = (os.networkInterfaces())[node.iface][0].address; + } else { + node.iface = (os.networkInterfaces())[node.iface][1].address; + } + } + } + catch(e) { + node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface})); + node.iface = null; + } + } + var opts = {type:node.ipv, reuseAddr:true}; if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } var server; @@ -39,7 +63,7 @@ module.exports = function(RED) { udpInputPortsInUse[this.port] = server; } else { - node.warn(RED._("udp.errors.alreadyused",node.port)); + node.warn(RED._("udp.errors.alreadyused",{port:node.port})); server = udpInputPortsInUse[this.port]; // re-use existing } @@ -121,12 +145,38 @@ module.exports = function(RED) { this.ipv = n.ipv || "udp4"; var node = this; + if (node.iface && node.iface.indexOf(".") === -1) { + try { + if ((os.networkInterfaces())[node.iface][0].hasOwnProperty("scopeid")) { + if (node.ipv === "udp4") { + node.iface = (os.networkInterfaces())[node.iface][1].address; + } else { + node.iface = (os.networkInterfaces())[node.iface][0].address; + } + } + else { + if (node.ipv === "udp4") { + node.iface = (os.networkInterfaces())[node.iface][0].address; + } else { + node.iface = (os.networkInterfaces())[node.iface][1].address; + } + } + } + catch(e) { + node.warn(RED._("udp.errors.ifnotfound",{iface:node.iface})); + node.iface = null; + } + } + var opts = {type:node.ipv, reuseAddr:true}; if (process.version.indexOf("v0.10") === 0) { opts = node.ipv; } var sock; - if (udpInputPortsInUse[this.outport || this.port]) { - sock = udpInputPortsInUse[this.outport || this.port]; + var p = this.port; + if (node.multicast != "false") { p = this.outport||"0"; } + if (udpInputPortsInUse[p]) { + sock = udpInputPortsInUse[p]; + node.log(RED._("udp.status.re-use",{outport:node.outport,host:node.addr,port:node.port})); } else { sock = dgram.createSocket(opts); // default to udp4 @@ -136,36 +186,35 @@ module.exports = function(RED) { // prevent it going to the global error handler and shutting node-red // down. }); - udpInputPortsInUse[this.outport || this.port] = sock; - } + udpInputPortsInUse[p] = sock; - if (node.multicast != "false") { - if (node.outport === "") { node.outport = node.port; } - sock.bind(node.outport, function() { // have to bind before you can enable broadcast... - sock.setBroadcast(true); // turn on broadcast - if (node.multicast == "multi") { - try { - sock.setMulticastTTL(128); - sock.addMembership(node.addr,node.iface); // Add to the multicast group - node.log(RED._("udp.status.mc-ready",{outport:node.outport,host:node.addr,port:node.port})); - } catch (e) { - if (e.errno == "EINVAL") { - node.error(RED._("udp.errors.bad-mcaddress")); - } else if (e.errno == "ENODEV") { - node.error(RED._("udp.errors.interface")); - } else { - node.error(RED._("udp.errors.error",{error:e.errno})); + if (node.multicast != "false") { + sock.bind(node.outport, function() { // have to bind before you can enable broadcast... + sock.setBroadcast(true); // turn on broadcast + if (node.multicast == "multi") { + try { + sock.setMulticastTTL(128); + sock.addMembership(node.addr,node.iface); // Add to the multicast group + node.log(RED._("udp.status.mc-ready",{iface:node.iface,outport:node.outport,host:node.addr,port:node.port})); + } catch (e) { + if (e.errno == "EINVAL") { + node.error(RED._("udp.errors.bad-mcaddress")); + } else if (e.errno == "ENODEV") { + node.error(RED._("udp.errors.interface")); + } else { + node.error(RED._("udp.errors.error",{error:e.errno})); + } } + } else { + node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port})); } - } else { - node.log(RED._("udp.status.bc-ready",{outport:node.outport,host:node.addr,port:node.port})); - } - }); - } else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) { - sock.bind(node.outport); - node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port})); - } else { - node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port})); + }); + } else if ((node.outport !== "") && (!udpInputPortsInUse[node.outport])) { + sock.bind(node.outport); + node.log(RED._("udp.status.ready",{outport:node.outport,host:node.addr,port:node.port})); + } else { + node.log(RED._("udp.status.ready-nolocal",{host:node.addr,port:node.port})); + } } node.on("input", function(msg) { @@ -198,8 +247,8 @@ module.exports = function(RED) { }); node.on("close", function() { - if (udpInputPortsInUse.hasOwnProperty(node.outport || node.port)) { - delete udpInputPortsInUse[node.outport || node.port]; + if (udpInputPortsInUse.hasOwnProperty(p)) { + delete udpInputPortsInUse[p]; } try { sock.close(); diff --git a/nodes/core/locales/en-US/messages.json b/nodes/core/locales/en-US/messages.json index 9fc8f729b..62d2ea46a 100644 --- a/nodes/core/locales/en-US/messages.json +++ b/nodes/core/locales/en-US/messages.json @@ -495,15 +495,15 @@ "using": "using", "output": "Output", "group": "Group", - "interface": "Local IP", - "interfaceprompt": "(optional) local ip address to bind to", + "interface": "Local IF", "send": "Send a", "toport": "to port", "address": "Address", "decode-base64": "Decode Base64 encoded payload?" }, "placeholder": { - "interface": "(optional) ip address of eth0", + "interface": "(optional) local interface or address to bind to", + "interfaceprompt": "(optional) local interface or address to bind to", "address": "destination ip" }, "udpmsgs": "udp messages", @@ -531,10 +531,11 @@ "mc-group": "udp multicast group __group__", "listener-stopped": "udp listener stopped", "output-stopped": "udp output stopped", - "mc-ready": "udp multicast ready: __outport__ -> __host__:__port__", + "mc-ready": "udp multicast ready: __iface__:__outport__ -> __host__:__port__", "bc-ready": "udp broadcast ready: __outport__ -> __host__:__port__", "ready": "udp ready: __outport__ -> __host__:__port__", - "ready-nolocal": "udp ready: __host__:__port__" + "ready-nolocal": "udp ready: __host__:__port__", + "re-use": "udp re-use socket: __outport__ -> __host__:__port__" }, "errors": { "access-error": "UDP access error, you may need root access for ports below 1024", @@ -544,7 +545,8 @@ "ip-notset": "udp: ip address not set", "port-notset": "udp: port not set", "port-invalid": "udp: port number not valid", - "alreadyused": "udp: port already in use" + "alreadyused": "udp: port __port__ already in use", + "ifnotfound": "udp: interface __iface__ not found" } }, "switch": { diff --git a/nodes/core/locales/ja/messages.json b/nodes/core/locales/ja/messages.json index 83b29565b..65a860ab1 100644 --- a/nodes/core/locales/ja/messages.json +++ b/nodes/core/locales/ja/messages.json @@ -526,7 +526,8 @@ "mc-ready": "udpノードはマルチキャストの準備ができています: __outport__ -> __host__:__port__", "bc-ready": "udpノードはブロードキャストの準備ができています: __outport__ -> __host__:__port__", "ready": "udpノードは準備ができています: __outport__ -> __host__:__port__", - "ready-nolocal": "udpノードは準備ができています: __host__:__port__" + "ready-nolocal": "udpノードは準備ができています: __host__:__port__", + "re-use": "udp再利用ソケット: __outport__ -> __host__:__port__" }, "errors": { "access-error": "UDP接続エラー 管理者権限で1024未満のポート番号にアクセスできる必要があります", diff --git a/nodes/core/locales/zh-CN/messages.json b/nodes/core/locales/zh-CN/messages.json index ebac0ceda..f674e5417 100644 --- a/nodes/core/locales/zh-CN/messages.json +++ b/nodes/core/locales/zh-CN/messages.json @@ -525,7 +525,8 @@ "mc-ready": "udp 组播已准备好: __outport__ -> __host__:__port__", "bc-ready": "udp 广播已准备好: __outport__ -> __host__:__port__", "ready": "udp 已准备好: __outport__ -> __host__:__port__", - "ready-nolocal": "udp 已准备好: __host__:__port__" + "ready-nolocal": "udp 已准备好: __host__:__port__", + "re-use": "udp 重用套接字: __outport__ -> __host__:__port__" }, "errors": { "access-error": "UDP 访问错误, 你可能需要root权限才能接入1024以下的端口", From ae4b1b17a9f1d204a0111c025c94cdf76443796f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Tue, 1 May 2018 12:59:53 +0100 Subject: [PATCH 11/40] Increase trigger node test timings --- test/nodes/core/core/89-trigger_spec.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/nodes/core/core/89-trigger_spec.js b/test/nodes/core/core/89-trigger_spec.js index 3d95234d5..b07072bcd 100644 --- a/test/nodes/core/core/89-trigger_spec.js +++ b/test/nodes/core/core/89-trigger_spec.js @@ -419,7 +419,7 @@ describe('trigger node', function() { }); it('should be able to extend the delay (but with no 2nd output)', function(done) { - var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"50", wires:[["n2"]] }, + var flow = [{"id":"n1", "type":"trigger", "name":"triggerNode", extend:"true", op1type:"pay", op2type:"nul", op1:"false", op2:"true", duration:"100", wires:[["n2"]] }, {id:"n2", type:"helper"} ]; helper.load(triggerNode, flow, function() { var n1 = helper.getNode("n1"); @@ -434,7 +434,7 @@ describe('trigger node', function() { else { msg.should.have.a.property("payload", "World"); //console.log(Date.now() - ss); - (Date.now() - ss).should.be.greaterThan(70); + (Date.now() - ss).should.be.greaterThan(140); done(); } } @@ -447,7 +447,7 @@ describe('trigger node', function() { },20); setTimeout( function() { n1.emit("input", {payload:"World"}); - },80); + },150); }); }); From 27bf72372ecaadeee410b95e957f56b725cbe206 Mon Sep 17 00:00:00 2001 From: Martin Guillon Date: Tue, 1 May 2018 14:00:05 +0200 Subject: [PATCH 12/40] fix after comments --- nodes/core/core/80-function.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodes/core/core/80-function.js b/nodes/core/core/80-function.js index 2d5f7901a..ba52f2ff6 100644 --- a/nodes/core/core/80-function.js +++ b/nodes/core/core/80-function.js @@ -205,7 +205,7 @@ module.exports = function(RED) { var context = vm.createContext(sandbox); try { this.script = vm.createScript(functionText, { - filename: this.name + '_' + this.id, // filename for stack traces + filename: 'Function Node:'+this.id+(this.name?' ['+this.name+']':'', // filename for stack traces lineOffset: -11, // line number offset to be used for stack traces columnOffset: 0, // column number offset to be used for stack traces displayErrors: true @@ -227,7 +227,6 @@ module.exports = function(RED) { //remove unwanted part const index = err.stack.search(/\n\s*at ContextifyScript.Script.runInContext/); err.stack = err.stack.slice(0, index).split('\n').slice(0,-1).join('\n'); - console.log(index, err.stack); const stack = err.stack.split(/\r?\n/); //store the error in msg to be used in flows From e3520309fca48efc3fc096a95bbe729d5bd2a67f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 May 2018 13:38:50 +0100 Subject: [PATCH 13/40] Add clone project to welcome screen --- editor/js/ui/projects/projects.js | 392 +++++++++++++++++- editor/sass/projects.scss | 12 +- .../localfilesystem/projects/git/index.js | 6 +- 3 files changed, 393 insertions(+), 17 deletions(-) diff --git a/editor/js/ui/projects/projects.js b/editor/js/ui/projects/projects.js index 95c2344b7..8811a5f7f 100644 --- a/editor/js/ui/projects/projects.js +++ b/editor/js/ui/projects/projects.js @@ -80,6 +80,26 @@ RED.projects = (function() { $('

').text("To get started you can create your first project or clone an existing project from a git repository.").appendTo(body); $('

').text("If you are not sure, you can skip this for now. You will still be able to create your first project from the 'Projects' menu at any time.").appendTo(body); + var row = $('

').appendTo(body); + var createAsEmpty = $('').appendTo(row); + var createAsClone = $('').appendTo(row); + + createAsEmpty.click(function(e) { + e.preventDefault(); + createProjectOptions = { + action: "create" + } + show('git-config'); + }) + + createAsClone.click(function(e) { + e.preventDefault(); + createProjectOptions = { + action: "clone" + } + show('git-config'); + }) + return container; }, buttons: [ @@ -90,13 +110,6 @@ RED.projects = (function() { createProjectOptions = {}; $( this ).dialog( "close" ); } - }, - { - text: "Create your first project", // TODO: nls - class: "primary", - click: function() { - show('git-config'); - } } ] }, @@ -170,7 +183,11 @@ RED.projects = (function() { currentGitSettings.user.name = gitUsernameInput.val(); currentGitSettings.user.email = gitEmailInput.val(); RED.settings.set('git', currentGitSettings); - show('project-details'); + if (createProjectOptions.action === "create") { + show('project-details'); + } else if (createProjectOptions.action === "clone") { + show('clone-project'); + } } } ] @@ -303,6 +320,365 @@ RED.projects = (function() { } }; })(), + 'clone-project': (function() { + var projectNameInput; + var projectSummaryInput; + var projectFlowFileInput; + var projectSecretInput; + var projectSecretSelect; + var copyProject; + var projectRepoInput; + var projectCloneSecret; + var emptyProjectCredentialInput; + var projectRepoUserInput; + var projectRepoPasswordInput; + var projectNameSublabel; + var projectRepoSSHKeySelect; + var projectRepoPassphrase; + var projectRepoRemoteName + var projectRepoBranch; + var selectedProject; + + return { + content: function(options) { + var container = $('
'); + migrateProjectHeader.appendTo(container); + var body = $('
').appendTo(container); + $('

').text("Clone a project").appendTo(body); + $('

').text("If you already have a git repository containing a project, you can clone it to get started.").appendTo(body); + + var projectList = null; + var pendingFormValidation = false; + $.getJSON("projects", function(data) { + projectList = {}; + data.projects.forEach(function(p) { + projectList[p] = true; + if (pendingFormValidation) { + pendingFormValidation = false; + validateForm(); + } + }) + }); + + + var validateForm = function() { + var projectName = projectNameInput.val(); + var valid = true; + if (projectNameInputChanged) { + if (projectList === null) { + pendingFormValidation = true; + return; + } + projectNameStatus.empty(); + if (!/^[a-zA-Z0-9\-_]+$/.test(projectName) || projectList[projectName]) { + projectNameInput.addClass("input-error"); + $('').appendTo(projectNameStatus); + projectNameValid = false; + valid = false; + if (projectList[projectName]) { + projectNameSublabel.text("Project already exists"); + } else { + projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); + } + } else { + projectNameInput.removeClass("input-error"); + $('').appendTo(projectNameStatus); + projectNameSublabel.text("Must contain only A-Z 0-9 _ -"); + projectNameValid = true; + } + projectNameLastChecked = projectName; + } + valid = projectNameValid; + + var repo = projectRepoInput.val(); + + // var validRepo = /^(?:file|git|ssh|https?|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?[\w\.@:\/~_-]+(?:\/?|\#[\d\w\.\-_]+?)$/.test(repo); + var validRepo = repo.length > 0 && !/\s/.test(repo); + if (/^https?:\/\/[^/]+@/i.test(repo)) { + $("#projects-dialog-screen-create-project-repo-label small").text("Do not include the username/password in the url"); + validRepo = false; + } + if (!validRepo) { + if (projectRepoChanged) { + projectRepoInput.addClass("input-error"); + } + valid = false; + } else { + projectRepoInput.removeClass("input-error"); + } + if (/^https?:\/\//.test(repo)) { + $(".projects-dialog-screen-create-row-creds").show(); + $(".projects-dialog-screen-create-row-sshkey").hide(); + } else if (/^(?:ssh|[\S]+?@[\S]+?):(?:\/\/)?/.test(repo)) { + $(".projects-dialog-screen-create-row-creds").hide(); + $(".projects-dialog-screen-create-row-sshkey").show(); + // if ( !getSelectedSSHKey(projectRepoSSHKeySelect) ) { + // valid = false; + // } + } else { + $(".projects-dialog-screen-create-row-creds").hide(); + $(".projects-dialog-screen-create-row-sshkey").hide(); + } + + $("#projects-dialog-clone-project").prop('disabled',!valid).toggleClass('disabled ui-button-disabled ui-state-disabled',!valid); + } + + var row; + + row = $('

').appendTo(body); + $('').appendTo(row); + + var subrow = $('
').appendTo(row); + projectNameInput = $('').appendTo(subrow); + var projectNameStatus = $('
').appendTo(subrow); + + var projectNameInputChanged = false; + var projectNameLastChecked = ""; + var projectNameValid; + var checkProjectName; + var autoInsertedName = ""; + + + projectNameInput.on("change keyup paste",function() { + projectNameInputChanged = (projectNameInput.val() !== projectNameLastChecked); + if (checkProjectName) { + clearTimeout(checkProjectName); + } else if (projectNameInputChanged) { + projectNameStatus.empty(); + $('').appendTo(projectNameStatus); + if (projectNameInput.val() === '') { + validateForm(); + return; + } + } + checkProjectName = setTimeout(function() { + validateForm(); + checkProjectName = null; + },300) + }); + projectNameSublabel = $('').appendTo(row).find("small"); + + row = $('
').appendTo(body); + $('').appendTo(row); + projectRepoInput = $('').appendTo(row); + $('').appendTo(row); + var projectRepoChanged = false; + var lastProjectRepo = ""; + projectRepoInput.on("change keyup paste",function() { + projectRepoChanged = true; + var repo = $(this).val(); + if (lastProjectRepo !== repo) { + $("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); + } + lastProjectRepo = repo; + + var m = /\/([^/]+?)(?:\.git)?$/.exec(repo); + if (m) { + var projectName = projectNameInput.val(); + if (projectName === "" || projectName === autoInsertedName) { + autoInsertedName = m[1]; + projectNameInput.val(autoInsertedName); + projectNameInput.change(); + } + } + validateForm(); + }); + + var cloneAuthRows = $('
').appendTo(body); + row = $('
').hide().appendTo(cloneAuthRows); + $('
Authentication failed
').appendTo(row); + + // Repo credentials - username/password ---------------- + row = $('
').hide().appendTo(cloneAuthRows); + + var subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoUserInput = $('').appendTo(subrow); + + subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoPasswordInput = $('').appendTo(subrow); + // ----------------------------------------------------- + + // Repo credentials - key/passphrase ------------------- + row = $('
').hide().appendTo(cloneAuthRows); + subrow = $('
').appendTo(row); + $('').appendTo(subrow); + projectRepoSSHKeySelect = $("').appendTo(subrow); + + subrow = $('
').appendTo(cloneAuthRows); + var sshwarningRow = $('
').hide().appendTo(subrow); + $('
Before you can clone a repository over ssh you must add an SSH key to access it.
').appendTo(sshwarningRow); + subrow = $('
').appendTo(sshwarningRow); + $('').appendTo(subrow).click(function(e) { + e.preventDefault(); + $('#projects-dialog-cancel').click(); + RED.userSettings.show('gitconfig'); + setTimeout(function() { + $("#user-settings-gitconfig-add-key").click(); + },500); + }); + // ----------------------------------------------------- + + + // Secret - clone + row = $('
').appendTo(body); + $('').appendTo(row); + projectSecretInput = $('').appendTo(row); + + + + return container; + }, + buttons: function(options) { + return [ + { + text: "Back", + click: function() { + show('git-config'); + } + }, + { + id: "projects-dialog-clone-project", + disabled: true, + text: "Clone project", // TODO: nls + class: "primary disabled", + click: function() { + var projectType = $(".projects-dialog-screen-create-type.selected").data('type'); + var projectData = { + name: projectNameInput.val(), + } + projectData.credentialSecret = projectSecretInput.val(); + var repoUrl = projectRepoInput.val(); + var metaData = {}; + if (/^(?:ssh|[\d\w\.\-_]+@[\w\.]+):(?:\/\/)?/.test(repoUrl)) { + var selected = projectRepoSSHKeySelect.val();//false;//getSelectedSSHKey(projectRepoSSHKeySelect); + if ( selected ) { + projectData.git = { + remotes: { + 'origin': { + url: repoUrl, + keyFile: selected, + passphrase: projectRepoPassphrase.val() + } + } + }; + } + else { + console.log("Error! Can't get selected SSH key path."); + return; + } + } + else { + projectData.git = { + remotes: { + 'origin': { + url: repoUrl, + username: projectRepoUserInput.val(), + password: projectRepoPasswordInput.val() + } + } + }; + } + + $(".projects-dialog-screen-create-row-auth-error").hide(); + + projectRepoUserInput.removeClass("input-error"); + projectRepoPasswordInput.removeClass("input-error"); + projectRepoSSHKeySelect.removeClass("input-error"); + projectRepoPassphrase.removeClass("input-error"); + + RED.deploy.setDeployInflight(true); + RED.projects.settings.switchProject(projectData.name); + + sendRequest({ + url: "projects", + type: "POST", + handleAuthFail: false, + responses: { + 200: function(data) { + dialog.dialog( "close" ); + }, + 400: { + 'project_exists': function(error) { + console.log("already exists"); + }, + 'git_error': function(error) { + console.log("git error",error); + }, + 'git_connection_failed': function(error) { + projectRepoInput.addClass("input-error"); + $("#projects-dialog-screen-create-project-repo-label small").text("Connection failed"); + }, + 'git_not_a_repository': function(error) { + projectRepoInput.addClass("input-error"); + $("#projects-dialog-screen-create-project-repo-label small").text("Not a git repository"); + }, + 'git_repository_not_found': function(error) { + projectRepoInput.addClass("input-error"); + $("#projects-dialog-screen-create-project-repo-label small").text("Repository not found"); + }, + 'git_auth_failed': function(error) { + $(".projects-dialog-screen-create-row-auth-error").show(); + + projectRepoUserInput.addClass("input-error"); + projectRepoPasswordInput.addClass("input-error"); + // getRepoAuthDetails(req); + projectRepoSSHKeySelect.addClass("input-error"); + projectRepoPassphrase.addClass("input-error"); + }, + 'missing_flow_file': function(error) { + // This is handled via a runtime notification. + dialog.dialog("close"); + }, + 'project_empty': function(error) { + // This is handled via a runtime notification. + dialog.dialog("close"); + }, + 'credentials_load_failed': function(error) { + // This is handled via a runtime notification. + dialog.dialog("close"); + }, + '*': function(error) { + reportUnexpectedError(error); + $( dialog ).dialog( "close" ); + } + } + } + },projectData).then(function() { + RED.events.emit("project:change", {name:name}); + }).always(function() { + setTimeout(function() { + RED.deploy.setDeployInflight(false); + },500); + }) + + } + } + ] + } + } + })(), 'default-files': (function() { var projectFlowFileInput; var projectCredentialFileInput; diff --git a/editor/sass/projects.scss b/editor/sass/projects.scss index d9b2545ac..74fa801c9 100644 --- a/editor/sass/projects.scss +++ b/editor/sass/projects.scss @@ -139,16 +139,17 @@ } } button.editor-button { - width: calc(50% - 40px); + width: calc(50% - 80px); margin: 20px; - height: 175px; + height: auto; line-height: 2em; - font-size: 1.5em !important; + padding: 10px; + border-color: #aaa; i { - color: #ccc; + color: #aaa; } &:hover i { - color: #aaa; + color: #999; } } .button-group { @@ -160,7 +161,6 @@ button.projects-dialog-screen-create-type { height: auto; padding: 10px; - } .button-group { text-align: center; diff --git a/red/runtime/storage/localfilesystem/projects/git/index.js b/red/runtime/storage/localfilesystem/projects/git/index.js index 717a1a235..196aece83 100644 --- a/red/runtime/storage/localfilesystem/projects/git/index.js +++ b/red/runtime/storage/localfilesystem/projects/git/index.js @@ -48,7 +48,9 @@ function runGitCommand(args,cwd,env) { var err = new Error(stderr); err.stdout = stdout; err.stderr = stderr; - if (/fatal: could not read/i.test(stderr)) { + if(/Connection refused/i.test(stderr)) { + err.code = "git_connection_failed"; + } else if (/fatal: could not read/i.test(stderr)) { // Username/Password err.code = "git_auth_failed"; } else if(/HTTP Basic: Access denied/i.test(stderr)) { @@ -58,8 +60,6 @@ function runGitCommand(args,cwd,env) { } else if(/Host key verification failed/i.test(stderr)) { // TODO: handle host key verification errors separately err.code = "git_auth_failed"; - } else if(/Connection refused/i.test(stderr)) { - err.code = "git_connection_failed"; } else if (/commit your changes or stash/i.test(stderr)) { err.code = "git_local_overwrite"; } else if (/CONFLICT/.test(err.stdout)) { From c4d1ccb6f51e41b2c5e712f2b033eb1471b8561f Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 May 2018 13:59:39 +0100 Subject: [PATCH 14/40] Keep remote branch state in sync between editor and runtime --- editor/js/ui/projects/projectSettings.js | 9 +++++++++ editor/js/ui/projects/projects.js | 2 ++ editor/js/ui/projects/tab-versionControl.js | 14 +++++++++++++- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/editor/js/ui/projects/projectSettings.js b/editor/js/ui/projects/projectSettings.js index bc882c006..f07f2ba1a 100644 --- a/editor/js/ui/projects/projectSettings.js +++ b/editor/js/ui/projects/projectSettings.js @@ -1255,6 +1255,14 @@ RED.projects.settings = (function() { text: 'Delete remote', click: function() { notification.close(); + + if (activeProject.git.branches.remote && activeProject.git.branches.remote.indexOf(entry.name+"/") === 0) { + delete activeProject.git.branches.remote; + } + if (activeProject.git.branches.remoteAlt && activeProject.git.branches.remoteAlt.indexOf(entry.name+"/") === 0) { + delete activeProject.git.branches.remoteAlt; + } + var url = "projects/"+activeProject.name+"/remotes/"+entry.name; var options = { url: url, @@ -1276,6 +1284,7 @@ RED.projects.settings = (function() { activeProject.git.remotes[name] = remote; }); } + delete activeProject.git.branches.remoteAlt; RED.sidebar.versionControl.refresh(); }); }, diff --git a/editor/js/ui/projects/projects.js b/editor/js/ui/projects/projects.js index 8811a5f7f..022799ca9 100644 --- a/editor/js/ui/projects/projects.js +++ b/editor/js/ui/projects/projects.js @@ -602,6 +602,7 @@ RED.projects = (function() { } $(".projects-dialog-screen-create-row-auth-error").hide(); + $("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); projectRepoUserInput.removeClass("input-error"); projectRepoPasswordInput.removeClass("input-error"); @@ -1503,6 +1504,7 @@ RED.projects = (function() { } $(".projects-dialog-screen-create-row-auth-error").hide(); + $("#projects-dialog-screen-create-project-repo-label small").text("https://, ssh:// or file://"); projectRepoUserInput.removeClass("input-error"); projectRepoPasswordInput.removeClass("input-error"); diff --git a/editor/js/ui/projects/tab-versionControl.js b/editor/js/ui/projects/tab-versionControl.js index a31ecb613..44b602019 100644 --- a/editor/js/ui/projects/tab-versionControl.js +++ b/editor/js/ui/projects/tab-versionControl.js @@ -592,7 +592,10 @@ RED.sidebar.versionControl = (function() { closeBranchBox(); localCommitListShade.show(); $(this).addClass('selected'); + var activeProject = RED.projects.getActiveProject(); + $("#sidebar-version-control-repo-toolbar-set-upstream-row").toggle(!!activeProject.git.branches.remoteAlt); remoteBox.show(); + setTimeout(function() { remoteBox.css("height","265px"); },100); @@ -868,7 +871,8 @@ RED.sidebar.versionControl = (function() { if (activeProject.git.branches.remoteAlt) { url+="/"+activeProject.git.branches.remoteAlt; } - if ($("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked')) { + var setUpstream = $("#sidebar-version-control-repo-toolbar-set-upstream").prop('checked'); + if (setUpstream) { url+="?u=true" } utils.sendRequest({ @@ -880,6 +884,10 @@ RED.sidebar.versionControl = (function() { // done(error,null); }, 200: function(data) { + if (setUpstream && activeProject.git.branches.remoteAlt) { + activeProject.git.branches.remote = activeProject.git.branches.remoteAlt; + delete activeProject.git.branches.remoteAlt; + } refresh(true); closeRemoteBox(); }, @@ -928,6 +936,10 @@ RED.sidebar.versionControl = (function() { // done(error,null); }, 200: function(data) { + if (options.setUpstream && activeProject.git.branches.remoteAlt) { + activeProject.git.branches.remote = activeProject.git.branches.remoteAlt; + delete activeProject.git.branches.remoteAlt; + } refresh(true); closeRemoteBox(); }, From 53e3e08d702066fd45be8dae838df810f5b3e642 Mon Sep 17 00:00:00 2001 From: Nick O'Leary Date: Wed, 2 May 2018 16:24:58 +0100 Subject: [PATCH 15/40] Handle cloning a project without package.json --- editor/js/main.js | 12 ++++++ editor/js/ui/projects/projects.js | 38 +++++++++++++++++++ red/api/editor/locales/en-US/editor.json | 1 + .../localfilesystem/projects/Project.js | 36 ++++++++++-------- .../storage/localfilesystem/projects/index.js | 6 +++ 5 files changed, 78 insertions(+), 15 deletions(-) diff --git a/editor/js/main.js b/editor/js/main.js index f97374b0e..b900fd28f 100644 --- a/editor/js/main.js +++ b/editor/js/main.js @@ -214,6 +214,18 @@ } ] } + } else if (msg.error === "missing_package_file") { + if (RED.user.hasPermission("projects.write")) { + options.buttons = [ + { + text: "Create default package file", + click: function() { + persistentNotifications[notificationId].hideNotification(); + RED.projects.createDefaultPackageFile(); + } + } + ] + } } else if (msg.error === "project_empty") { if (RED.user.hasPermission("projects.write")) { options.buttons = [ diff --git a/editor/js/ui/projects/projects.js b/editor/js/ui/projects/projects.js index 022799ca9..d7c2787d8 100644 --- a/editor/js/ui/projects/projects.js +++ b/editor/js/ui/projects/projects.js @@ -2251,6 +2251,43 @@ RED.projects = (function() { createProjectOptions = {}; show('default-files',{existingProject: true}); } + function createDefaultPackageFile() { + RED.deploy.setDeployInflight(true); + RED.projects.settings.switchProject(activeProject.name); + + var method = "PUT"; + var url = "projects/"+activeProject.name; + var createProjectOptions = { + initialise: true + }; + sendRequest({ + url: url, + type: method, + requireCleanWorkspace: true, + handleAuthFail: false, + responses: { + 200: function(data) { }, + 400: { + 'git_error': function(error) { + console.log("git error",error); + }, + 'missing_flow_file': function(error) { + // This is a natural next error - but let the runtime event + // trigger the dialog rather than double-report it. + $( dialog ).dialog( "close" ); + }, + '*': function(error) { + reportUnexpectedError(error); + $( dialog ).dialog( "close" ); + } + } + } + },createProjectOptions).always(function() { + setTimeout(function() { + RED.deploy.setDeployInflight(false); + },500); + }) + } function refresh(done) { $.getJSON("projects",function(data) { @@ -2330,6 +2367,7 @@ RED.projects = (function() { RED.projects.settings.show('deps'); }, createDefaultFileSet: createDefaultFileSet, + createDefaultPackageFile: createDefaultPackageFile, // showSidebar: showSidebar, refresh: refresh, editProject: function() { diff --git a/red/api/editor/locales/en-US/editor.json b/red/api/editor/locales/en-US/editor.json index 189dbaba7..ed74b8232 100644 --- a/red/api/editor/locales/en-US/editor.json +++ b/red/api/editor/locales/en-US/editor.json @@ -93,6 +93,7 @@ "credentials_load_failed": "

Flows stopped as the credentials could not be decrypted.

The flow credential file is encrypted, but the project's encryption key is missing or invalid.

", "credentials_load_failed_reset":"

Credentials could not be decrypted

The flow credential file is encrypted, but the project's encryption key is missing or invalid.

The flow credential file will be reset on the next deployment. Any existing flow credentials will be cleared.

", "missing_flow_file": "

Project flow file not found.

The project is not configured with a flow file.

", + "missing_package_file": "

Project package file not found.

The project is missing a package.json file.

", "project_empty": "

The project is empty.

Do you want to create a default set of project files?
Otherwise, you will have to manually add files to the project outside of the editor.

", "project_not_found": "

Project '__project__' not found.

", "git_merge_conflict": "

Automatic merging of changes failed.

Fix the unmerged conflicts then commit the results.

" diff --git a/red/runtime/storage/localfilesystem/projects/Project.js b/red/runtime/storage/localfilesystem/projects/Project.js index aa2693001..72682c25c 100644 --- a/red/runtime/storage/localfilesystem/projects/Project.js +++ b/red/runtime/storage/localfilesystem/projects/Project.js @@ -81,9 +81,7 @@ Project.prototype.load = function () { var promises = []; return checkProjectFiles(project).then(function(missingFiles) { - if (missingFiles.length > 0) { - project.missingFiles = missingFiles; - } + project.missingFiles = missingFiles; if (missingFiles.indexOf('package.json') === -1) { project.paths['package.json'] = fspath.join(project.path,"package.json"); promises.push(fs.readFile(project.paths['package.json'],"utf8").then(function(content) { @@ -135,9 +133,9 @@ Project.prototype.load = function () { Project.prototype.initialise = function(user,data) { var project = this; - if (!this.empty) { - throw new Error("Cannot initialise non-empty project"); - } + // if (!this.empty) { + // throw new Error("Cannot initialise non-empty project"); + // } var files = Object.keys(defaultFileSet); var promises = []; @@ -148,17 +146,25 @@ Project.prototype.initialise = function(user,data) { promises.push(settings.set('projects',projects)); } - project.files.flow = data.files.flow; - project.files.credentials = data.files.credentials; - var flowFilePath = fspath.join(project.path,project.files.flow); - var credsFilePath = getCredentialsFilename(flowFilePath); - promises.push(util.writeFile(flowFilePath,"[]")); - promises.push(util.writeFile(credsFilePath,"{}")); - files.push(project.files.flow); - files.push(project.files.credentials); + if (data.hasOwnProperty('files')) { + if (data.files.hasOwnProperty('flow') && data.files.hasOwnProperty('credentials')) { + project.files.flow = data.files.flow; + project.files.credentials = data.files.credentials; + var flowFilePath = fspath.join(project.path,project.files.flow); + var credsFilePath = getCredentialsFilename(flowFilePath); + promises.push(util.writeFile(flowFilePath,"[]")); + promises.push(util.writeFile(credsFilePath,"{}")); + files.push(project.files.flow); + files.push(project.files.credentials); + } + } for (var file in defaultFileSet) { if (defaultFileSet.hasOwnProperty(file)) { - promises.push(util.writeFile(fspath.join(project.path,file),defaultFileSet[file](project))); + var path = fspath.join(project.path,file); + if (!fs.existsSync(path)) { + promises.push(util.writeFile(path,defaultFileSet[file](project))); + } + } } diff --git a/red/runtime/storage/localfilesystem/projects/index.js b/red/runtime/storage/localfilesystem/projects/index.js index 7c24845ab..4c7539260 100644 --- a/red/runtime/storage/localfilesystem/projects/index.js +++ b/red/runtime/storage/localfilesystem/projects/index.js @@ -479,6 +479,12 @@ function getFlows() { error.code = "project_empty"; return when.reject(error); } + if (activeProject.missingFiles && activeProject.missingFiles.indexOf('package.json') !== -1) { + log.warn("Project missing package.json"); + error = new Error("Project missing package.json"); + error.code = "missing_package_file"; + return when.reject(error); + } if (!activeProject.getFlowFile()) { log.warn("Project has no flow file"); error = new Error("Project has no flow file"); From f478d7c9f05b67d1d446218842807b0738c88ada Mon Sep 17 00:00:00 2001 From: mblackstock Date: Wed, 2 May 2018 15:15:58 -0700 Subject: [PATCH 16/40] experiments with mqtt ui (wip) --- nodes/core/io/10-mqtt.html | 181 ++++++++++++++++++------- nodes/core/locales/en-US/messages.json | 10 +- 2 files changed, 139 insertions(+), 52 deletions(-) diff --git a/nodes/core/io/10-mqtt.html b/nodes/core/io/10-mqtt.html index db3866f28..f73b0e76e 100644 --- a/nodes/core/io/10-mqtt.html +++ b/nodes/core/io/10-mqtt.html @@ -212,48 +212,89 @@
-