Skip to content

Commit

Permalink
add before state change event
Browse files Browse the repository at this point in the history
  • Loading branch information
mihaidma committed Jan 29, 2016
1 parent 5b3186c commit 85eb728
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 16 deletions.
12 changes: 10 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ Configuration structure for state machine is:
* _defaults_ default behavior for this state - TBD
* _initState_ default state for state machine. One single state should have this parameter true
* _events_ allows adding event hooks trigered when the state changes
* _after_ called after a state changed - can be the child of the root _states_ object or child of a state
* _pattern_ seneca pattern defining the action to be called after the state changed
* _before_ called before the state execution - can be the child of the root _states_ object or child of a state
* _pattern_ seneca pattern defining the action to be called before the state execution
* _after_ called after a state is executed - can be the child of the root _states_ object or child of a state
* _pattern_ seneca pattern defining the action to be called after the state execution
* _commands_ array with all commands for current state
* _key_ command to be executed for this state
* _pattern_ seneca pattern defining the action to be called to execute the state
Expand Down Expand Up @@ -118,6 +120,9 @@ The configuration to be used for this state machine is:
name: 'sm1',
states: {
events: {
before: {
pattern: "role: 'transport', execute: 'before_any_state_change'"
},
after: {
pattern: "role: 'transport', execute: 'after_any_state_change'"
}
Expand Down Expand Up @@ -162,6 +167,9 @@ The configuration to be used for this state machine is:
}
},
events: {
before: {
pattern: "role: 'transport', execute: 'before_notconfigured_state_change'"
},
after: {
pattern: "role: 'transport', execute: 'after_notconfigured_state_change'"
}
Expand Down
9 changes: 9 additions & 0 deletions lib/rules.js
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ module.exports = {
return null
},

retrieve_before_command: function (config, state) {
var events = this.retrieve_events(config, state)
if (events && events.before) {
return events.before
}

return null
},

retrieve_after_command: function (config, state) {
var events = this.retrieve_events(config, state)
if (events && events.after) {
Expand Down
18 changes: 11 additions & 7 deletions lib/sm.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ module.exports = function (options) {
}

StateHelper.change(context, args.state)
var afterCommand = ConfigurationHelper.retrieve_after_command(context.config, context.current_status)
if (afterCommand) {
seneca.act(afterCommand.pattern, function(err, result) {
})
}

return done(null, _.clone(context))
}
Expand All @@ -103,11 +98,21 @@ module.exports = function (options) {
var seneca = this
var context = internals[args.role]
var command = ConfigurationHelper.retrieve_command(context.config, context.current_status, args.cmd)
var beforeCommand = ConfigurationHelper.retrieve_before_command(context.config, context.current_status)
var afterCommand = ConfigurationHelper.retrieve_after_command(context.config, context.current_status)

delete args.role
delete args.cmd

Async.series({
before_command: function (callback) {
if (!beforeCommand) {
return callback()
}
seneca.act(beforeCommand.pattern, function(err, data) {
callback(err, data)
})
},
command: function (callback) {
seneca.act(command.pattern, args, function (err, data) {
StateHelper.findNextState(command, err, data, function (state_err, nextState) {
Expand All @@ -120,7 +125,6 @@ module.exports = function (options) {
})
},
after_command: function (callback) {
var afterCommand = ConfigurationHelper.retrieve_after_command(context.config, context.current_status)
if (!afterCommand) {
return callback()
}
Expand All @@ -129,7 +133,7 @@ module.exports = function (options) {
})
}
}, function (err, results) {
// the "command" results are returned, the "after_command" ones not returned
// the "command" results are returned, the "before_command" and "after_command" ones are not returned
done(err, results.command)
})
}
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "lib/sm.js",
"scripts": {
"start": "lib/sm.js",
"test": "lab -v -P test -t 80",
"test": "lab -v -P test -t 79",
"cover": "lab -s -P test -r lcov | coveralls",
"lint": "lab -P test -dL"
},
Expand Down
50 changes: 44 additions & 6 deletions test/events.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,18 @@ suite('test local and global after state change events', function () {
}

test('test global after state change event', function (done) {
var beforeEventRaised = false
var afterEventRaised = false

// action to be called by the global after state change event pattern from config
seneca.add({role: 'transport', execute: 'before_state_change'}, function (args, done) {
beforeEventRaised = true

if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', before: true})
})
seneca.add({role: 'transport', execute: 'after_state_change'}, function (args, done) {
afterEventRaised = true

Expand All @@ -57,17 +66,27 @@ suite('test local and global after state change events', function () {
})
},
init: function (callback) { verifyState('INIT', Util.config.name, callback) },
after_event_trigger: function (callback) {
before_after_event_trigger: function (callback) {
// go to the CONNECTED state, this fires the global after state change event pattern
var loadState = 'CONNECTED'
seneca.act('role: ' + Util.config.name + ', load: state', {sm_name: Util.config.name, state: loadState}, function (err, context) {
expect(err).to.not.exist()
expect(context).to.exist()
expect(context.current_status).to.equal(loadState)

callback(err)
// execute state and move to DISCONNECTED
seneca.act('role: ' + Util.config.name + ', cmd: disconnect', {shouldFail: false}, function (err, data) {
expect(beforeEventRaised).to.be.true()

expect(err).to.not.exist()
expect(data).to.exist()
expect(data.connect).to.exist()

callback(err)
})
})
},
notconfigured: function (callback) { verifyState('CONNECTED', Util.config.name, callback) },
notconfigured: function (callback) { verifyState('DISCONNECTED', Util.config.name, callback) },
verify_after_event: function (callback) {
expect(afterEventRaised).to.be.true()

Expand All @@ -81,9 +100,18 @@ suite('test local and global after state change events', function () {
})

test('test local after state change event', function (done) {
var localBeforeEventRaised = false
var localAfterEventRaised = false

// actions to be called by the global after state change event patterns from config
seneca.add({role: 'transport', execute: 'before_notconfigured_state_change'}, function (args, done) {
localBeforeEventRaised = true

if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', before_notconfigured: true})
})
seneca.add({role: 'transport', execute: 'after_notconfigured_state_change'}, function (args, done) {
localAfterEventRaised = true

Expand All @@ -104,16 +132,26 @@ suite('test local and global after state change events', function () {
})
},
init: function (callback) { verifyState('INIT', sm2Config.name, callback) },
after_event_trigger: function (callback) {
// go to the NOT_CONFIGURED state, this fires the after state change event
go_not_configured: function (callback) {
seneca.act("role: '" + sm2Config.name + "', cmd: 'execute'", {shouldFail: false}, function (err, data) {
expect(err).to.not.exist()
expect(data).to.exist()
expect(data.connect).to.exist()
callback(err)
})
},
notconfigured: function (callback) { verifyState('NOT_CONFIGURED', sm2Config.name, callback) },
check_not_configured: function (callback) { verifyState('NOT_CONFIGURED', sm2Config.name, callback) },
local_before_after_trigger: function (callback) {
// go to the NOT_CONFIGURED state, this fires the after state change event
seneca.act("role: '" + sm2Config.name + "', cmd: 'execute'", {shouldFail: false}, function (err, data) {
expect(localBeforeEventRaised).to.be.true()

expect(err).to.not.exist()
expect(data).to.exist()
callback(err)
})
},
verify_connected: function (callback) { verifyState('CONNECTED', sm2Config.name, callback) },
verify_after_event: function (callback) {
expect(localAfterEventRaised).to.be.true()

Expand Down
21 changes: 21 additions & 0 deletions test/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ exports.config = {

states: {
events: {
before: {
pattern: "role: 'transport', execute: 'before_state_change'"
},
after: {
pattern: "role: 'transport', execute: 'after_state_change'"
}
Expand Down Expand Up @@ -55,6 +58,9 @@ exports.config = {
}
},
events: {
before: {
pattern: "role: 'transport', execute: 'before_notconfigured_state_change'"
},
after: {
pattern: "role: 'transport', execute: 'after_notconfigured_state_change'"
}
Expand Down Expand Up @@ -123,18 +129,33 @@ exports.init = function (options, cb) {
done(null, {data: 'OK', command: true})
})

si.add({role: 'transport', execute: 'before_state_change'}, function (args, done) {
if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', before: true})
})

si.add({role: 'transport', execute: 'after_state_change'}, function (args, done) {
if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', after: true})
})

si.add({role: 'transport', execute: 'before_notconfigured_state_change'}, function (args, done) {
if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', before_notconfigured: true})
})

si.add({role: 'transport', execute: 'after_notconfigured_state_change'}, function (args, done) {
if (args.shouldFail) {
return done('Some error')
}
done(null, {data: 'OK', after_notconfigured: true})
})

cb(null, si)
}

0 comments on commit 85eb728

Please sign in to comment.