Merge pull request #3719 from node-red/pr_3642

Allow flows to be stopped and started manually
This commit is contained in:
Nick O'Leary
2022-06-29 20:54:45 +01:00
committed by GitHub
23 changed files with 565 additions and 71 deletions

View File

@@ -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')
.send({state:'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')
.send({state:'bad-state'})
.expect(400)
.end(function(err,res) {
if (err) {
return done(err);
}
res.body.should.have.property("code","invalid_run_state");
done();
});
});
});

View File

@@ -427,4 +427,123 @@ describe("runtime-api/flows", function() {
});
});
describe("flow run state", function() {
var startFlows, stopFlows, runtime;
beforeEach(function() {
let flowsStarted = true;
let flowsState = "start";
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 = "start";
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 = "stop";
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:[]} },
state: function() { return flowsState}
}
}
})
it("gets flows run state", async function() {
flows.init(runtime);
const state = await flows.getState({})
state.should.have.property("state", "start")
});
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("state", "start")
});
it("start flows", async function() {
flows.init(runtime);
const state = await flows.setState({state:"start"})
state.should.have.property("state", "start")
stopFlows.called.should.not.be.true();
startFlows.called.should.be.true();
});
it("stop flows", async function() {
flows.init(runtime);
const state = await flows.setState({state:"stop"})
state.should.have.property("state", "stop")
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({state:"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({state:"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({state:"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)
});
});
});

View File

@@ -396,12 +396,13 @@ describe('flows/index', function() {
try {
flowCreate.called.should.be.false();
receivedEvent.should.have.property('id','runtime-state');
receivedEvent.should.have.property('payload',
{ error: 'missing-modules',
type: 'warning',
text: 'notification.warnings.missing-modules',
modules: [] }
);
receivedEvent.should.have.property('payload', {
state: 'stop',
error: 'missing-modules',
type: 'warning',
text: 'notification.warnings.missing-modules',
modules: []
});
done();
}catch(err) {