mirror of
https://github.com/node-red/node-red.git
synced 2025-03-01 10:36:34 +00:00
implement flows runtime stop/start API and UI
This commit is contained in:
@@ -32,7 +32,9 @@ describe("api/admin/flows", function() {
|
||||
app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.get("/flows",flows.get);
|
||||
app.get("/flows/state",flows.getState);
|
||||
app.post("/flows",flows.post);
|
||||
app.post("/flows/state",flows.postState);
|
||||
});
|
||||
|
||||
it('returns flow - v1', function(done) {
|
||||
@@ -208,4 +210,99 @@ describe("api/admin/flows", function() {
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('returns flows run state', function (done) {
|
||||
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||
flows.init({
|
||||
flows: {
|
||||
setFlows,
|
||||
getState: async function () {
|
||||
return { started: true, state: "started" };
|
||||
}
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.get('/flows/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Node-RED-Deployment-Type', 'reload')
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
try {
|
||||
res.body.should.have.a.property('started', true);
|
||||
res.body.should.have.a.property('state', "started");
|
||||
done();
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('sets flows run state - stopped', function (done) {
|
||||
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||
flows.init({
|
||||
flows: {
|
||||
setFlows: setFlows,
|
||||
getState: async function () {
|
||||
return { started: true, state: "started" };
|
||||
},
|
||||
setState: async function () {
|
||||
return { started: false, state: "stopped" };
|
||||
},
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/flows/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Node-RED-Flow-Run-State-Change', 'stop')
|
||||
.expect(200)
|
||||
.end(function (err, res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
try {
|
||||
res.body.should.have.a.property('started', false);
|
||||
res.body.should.have.a.property('state', "stopped");
|
||||
done();
|
||||
} catch (e) {
|
||||
return done(e);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('sets flows run state - bad value', function (done) {
|
||||
var setFlows = sinon.spy(function () { return Promise.resolve(); });
|
||||
const makeError = (error, errcode, statusCode) => {
|
||||
const message = typeof error == "object" ? error.message : error
|
||||
const err = typeof error == "object" ? error : new Error(message||"Unexpected Error")
|
||||
err.status = err.status || statusCode || 400;
|
||||
err.code = err.code || errcode || "unexpected_error"
|
||||
return err
|
||||
}
|
||||
flows.init({
|
||||
flows: {
|
||||
setFlows: setFlows,
|
||||
getState: async function () {
|
||||
return { started: true, state: "started" };
|
||||
},
|
||||
setState: async function () {
|
||||
var err = (makeError("Cannot set runtime state. Invalid state", "invalid_run_state", 400))
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
},
|
||||
}
|
||||
});
|
||||
request(app)
|
||||
.post('/flows/state')
|
||||
.set('Accept', 'application/json')
|
||||
.set('Node-RED-Flow-Run-State-Change', 'bad-state')
|
||||
.expect(400)
|
||||
.end(function(err,res) {
|
||||
if (err) {
|
||||
return done(err);
|
||||
}
|
||||
res.body.should.have.property("code","invalid_run_state");
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -427,4 +427,126 @@ describe("runtime-api/flows", function() {
|
||||
});
|
||||
});
|
||||
|
||||
describe("flow run state", function() {
|
||||
var startFlows, stopFlows, runtime;
|
||||
beforeEach(function() {
|
||||
let flowsStarted = true;
|
||||
let flowsState = "started";
|
||||
startFlows = sinon.spy(function(type) {
|
||||
if (type !== "full") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
flowsStarted = true;
|
||||
flowsState = "started";
|
||||
return Promise.resolve();
|
||||
});
|
||||
stopFlows = sinon.spy(function(type) {
|
||||
if (type !== "full") {
|
||||
var err = new Error();
|
||||
// TODO: quirk of internal api - uses .code for .status
|
||||
err.code = 400;
|
||||
var p = Promise.reject(err);
|
||||
p.catch(()=>{});
|
||||
return p;
|
||||
}
|
||||
flowsStarted = false;
|
||||
flowsState = "stopped";
|
||||
return Promise.resolve();
|
||||
});
|
||||
runtime = {
|
||||
log: mockLog(),
|
||||
settings: {
|
||||
runtimeState: {
|
||||
enabled: true,
|
||||
ui: true,
|
||||
},
|
||||
},
|
||||
flows: {
|
||||
get started() {
|
||||
return flowsStarted;
|
||||
},
|
||||
startFlows,
|
||||
stopFlows,
|
||||
getFlows: function() { return {rev:"currentRev",flows:[]} },
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("gets flows run state", async function() {
|
||||
flows.init(runtime);
|
||||
const state = await flows.getState({})
|
||||
state.should.have.property("started", true)
|
||||
state.should.have.property("state", "started")
|
||||
});
|
||||
it("permits getting flows run state when setting disabled", async function() {
|
||||
runtime.settings.runtimeState.enabled = false;
|
||||
flows.init(runtime);
|
||||
const state = await flows.getState({})
|
||||
state.should.have.property("started", true)
|
||||
state.should.have.property("state", "started")
|
||||
});
|
||||
it("start flows", async function() {
|
||||
flows.init(runtime);
|
||||
const state = await flows.setState({requestedState:"start"})
|
||||
state.should.have.property("started", true)
|
||||
state.should.have.property("state", "started")
|
||||
stopFlows.called.should.not.be.true();
|
||||
startFlows.called.should.be.true();
|
||||
});
|
||||
it("stop flows", async function() {
|
||||
flows.init(runtime);
|
||||
const state = await flows.setState({requestedState:"stop"})
|
||||
state.should.have.property("started", false)
|
||||
state.should.have.property("state", "stopped")
|
||||
stopFlows.called.should.be.true();
|
||||
startFlows.called.should.not.be.true();
|
||||
});
|
||||
it("rejects starting flows when setting disabled", async function() {
|
||||
let err;
|
||||
runtime.settings.runtimeState.enabled = false;
|
||||
flows.init(runtime);
|
||||
try {
|
||||
await flows.setState({requestedState:"start"})
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
stopFlows.called.should.not.be.true();
|
||||
startFlows.called.should.not.be.true();
|
||||
should(err).have.property("code", "not_allowed")
|
||||
should(err).have.property("status", 405)
|
||||
});
|
||||
it("rejects stopping flows when setting disabled", async function() {
|
||||
let err;
|
||||
runtime.settings.runtimeState.enabled = false;
|
||||
flows.init(runtime);
|
||||
try {
|
||||
await flows.setState({requestedState:"stop"})
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
stopFlows.called.should.not.be.true();
|
||||
startFlows.called.should.not.be.true();
|
||||
should(err).have.property("code", "not_allowed")
|
||||
should(err).have.property("status", 405)
|
||||
});
|
||||
it("rejects setting invalid flows run state", async function() {
|
||||
let err;
|
||||
flows.init(runtime);
|
||||
try {
|
||||
await flows.setState({requestedState:"bad-state"})
|
||||
} catch (error) {
|
||||
err = error
|
||||
}
|
||||
stopFlows.called.should.not.be.true();
|
||||
startFlows.called.should.not.be.true();
|
||||
should(err).have.property("code", "invalid_run_state")
|
||||
should(err).have.property("status", 400)
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
@@ -131,7 +131,7 @@ describe('flows/index', function() {
|
||||
// eventsOn.calledOnce.should.be.true();
|
||||
// });
|
||||
// });
|
||||
|
||||
/*
|
||||
describe('#setFlows', function() {
|
||||
it('sets the full flow', function(done) {
|
||||
var originalConfig = [
|
||||
@@ -300,6 +300,7 @@ describe('flows/index', function() {
|
||||
});
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
describe('#startFlows', function() {
|
||||
it('starts the loaded config', function(done) {
|
||||
@@ -321,6 +322,87 @@ describe('flows/index', function() {
|
||||
return flows.startFlows();
|
||||
});
|
||||
});
|
||||
it('emits runtime-event "flows-run-state" "started"', async function () {
|
||||
var originalConfig = [
|
||||
{ id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||
{ id: "t1", type: "tab" }
|
||||
];
|
||||
storage.getFlows = function () {
|
||||
return Promise.resolve({ flows: originalConfig });
|
||||
}
|
||||
let receivedEvent = null;
|
||||
const handleEvent = (data) => {
|
||||
console.log(data)
|
||||
if(data && data.id === 'flows-run-state') {
|
||||
receivedEvent = data;
|
||||
}
|
||||
}
|
||||
events.on('runtime-event', handleEvent);
|
||||
flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||
await flows.load()
|
||||
await flows.startFlows()
|
||||
events.removeListener("runtime-event", handleEvent);
|
||||
|
||||
//{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||
should(receivedEvent).not.be.null()
|
||||
receivedEvent.should.have.property("id", "flows-run-state")
|
||||
receivedEvent.should.have.property("payload", { started: true, state: "started" })
|
||||
receivedEvent.should.have.property("retain", true)
|
||||
});
|
||||
it('emits runtime-event "flows-run-state" "stopped"', async function () {
|
||||
const originalConfig = [
|
||||
{ id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||
{ id: "t1", type: "tab" }
|
||||
];
|
||||
storage.getFlows = function () {
|
||||
return Promise.resolve({ flows: originalConfig });
|
||||
}
|
||||
let receivedEvent = null;
|
||||
const handleEvent = (data) => {
|
||||
if(data && data.id === 'flows-run-state') {
|
||||
receivedEvent = data;
|
||||
}
|
||||
}
|
||||
events.on('runtime-event', handleEvent);
|
||||
flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||
await flows.load()
|
||||
await flows.startFlows()
|
||||
await flows.stopFlows()
|
||||
events.removeListener("runtime-event", handleEvent);
|
||||
|
||||
//{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||
should(receivedEvent).not.be.null()
|
||||
receivedEvent.should.have.property("id", "flows-run-state")
|
||||
receivedEvent.should.have.property("payload", { started: false, state: "stopped" })
|
||||
receivedEvent.should.have.property("retain", true)
|
||||
});
|
||||
// it('raises error when invalid flows run state requested', async function () {
|
||||
// const originalConfig = [
|
||||
// { id: "t1-1", x: 10, y: 10, z: "t1", type: "test", wires: [] },
|
||||
// { id: "t1", type: "tab" }
|
||||
// ];
|
||||
// storage.getFlows = function () {
|
||||
// return Promise.resolve({ flows: originalConfig });
|
||||
// }
|
||||
// let receivedEvent = null;
|
||||
// const handleEvent = (data) => {
|
||||
// if(data && data.id === 'flows-run-state') {
|
||||
// receivedEvent = data;
|
||||
// }
|
||||
// }
|
||||
// events.on('runtime-event', handleEvent);
|
||||
// flows.init({ log: mockLog, settings: {}, storage: storage });
|
||||
// await flows.load()
|
||||
// await flows.startFlows()
|
||||
// await flows.stopFlows()
|
||||
// events.removeListener("runtime-event", handleEvent);
|
||||
|
||||
// //{id:"flows-run-state", payload: {started: true, state: "started"}
|
||||
// should(receivedEvent).not.be.null()
|
||||
// receivedEvent.should.have.property("id", "flows-run-state")
|
||||
// receivedEvent.should.have.property("payload", { started: false, state: "stopped" })
|
||||
// receivedEvent.should.have.property("retain", true)
|
||||
// });
|
||||
it('does not start if nodes missing', function(done) {
|
||||
var originalConfig = [
|
||||
{id:"t1-1",x:10,y:10,z:"t1",type:"test",wires:[]},
|
||||
@@ -415,7 +497,7 @@ describe('flows/index', function() {
|
||||
describe.skip('#get',function() {
|
||||
|
||||
});
|
||||
|
||||
/*
|
||||
describe('#eachNode', function() {
|
||||
it('iterates the flow nodes', function(done) {
|
||||
var originalConfig = [
|
||||
@@ -582,7 +664,7 @@ describe('flows/index', function() {
|
||||
];
|
||||
flows.init({log:mockLog, settings:{},storage:storage});
|
||||
flows.setFlows(originalConfig).then(function() {
|
||||
/*jshint immed: false */
|
||||
|
||||
try {
|
||||
flows.checkTypeInUse("used-module");
|
||||
done("type_in_use error not thrown");
|
||||
@@ -666,4 +748,5 @@ describe('flows/index', function() {
|
||||
describe('#enableFlow', function() {
|
||||
it.skip("enableFlow");
|
||||
})
|
||||
*/
|
||||
});
|
||||
|
Reference in New Issue
Block a user