Skip to content

Commit 315f953

Browse files
author
Tom Kirkpatrick
committed
Fixup return values and add more tests
1 parent 679c1c1 commit 315f953

File tree

4 files changed

+144
-24
lines changed

4 files changed

+144
-24
lines changed

lib/index.js

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,19 @@ module.exports = function loopbackComponentFsm(app) {
1717
return Model ? Model.__FSM_CONFIG__ : null
1818
}
1919

20+
function getModelName(instance) {
21+
return instance.constructor.definition.name
22+
}
23+
24+
function generateCacheKey(instance) {
25+
if (!instance.id) {
26+
return null
27+
}
28+
const modelName = getModelName(instance)
29+
30+
return `loopback-component-fsm.${modelName}.id:${instance.id}`
31+
}
32+
2033
/**
2134
* Fetch cached state machine for an instance.
2235
*/
@@ -25,14 +38,14 @@ module.exports = function loopbackComponentFsm(app) {
2538
return null
2639
}
2740

28-
const modelName = instance.constructor.definition.name
29-
const cacheKey = `loopback-component-fsm.${modelName}.id:${instance.id}`
41+
const modelName = getModelName(instance)
42+
const cacheKey = generateCacheKey(instance)
3043
let cache = _.get(app.locals, cacheKey)
3144

3245
debug('Fetching cached state machine found for subscription %s (cache key=%s)', instance.id, cacheKey)
3346

3447
if (cache) {
35-
debug('Cached state machine found for subscription %s', instance.id, cache)
48+
debug('Cached state machine found for subscription %s', instance.id)
3649
return cache
3750
}
3851

@@ -45,10 +58,35 @@ module.exports = function loopbackComponentFsm(app) {
4558

4659
config = Object.assign(config, { initial: instance.status })
4760
cache = new StateMachine(config)
61+
debug('Caching state machine subscription %s: %o', instance.id, cache)
4862
_.set(app.locals, cacheKey, cache)
4963

5064
return cache
5165
}
5266

67+
68+
/**
69+
* Fetch cached state machine for an instance.
70+
*/
71+
function deleteStateMachine(instance) {
72+
if (!instance.id) {
73+
return null
74+
}
75+
76+
const cacheKey = generateCacheKey(instance)
77+
78+
debug('Deleting cached state machine for subscription %s (cache key=%s)', instance.id, cacheKey)
79+
80+
if (!_.get(app.locals, cacheKey)) {
81+
debug('Existing cached state machine not found for subscription %s (cache key=%s)', instance.id, cacheKey)
82+
return false
83+
}
84+
85+
_.unset(app.locals, cacheKey)
86+
debug('Deleted cached state machine for subscription %s (cache key=%s)', instance.id, cacheKey)
87+
return true
88+
}
89+
5390
app.getStateMachine = getStateMachine
91+
app.deleteStateMachine = deleteStateMachine
5492
}

lib/mixins/state-machine.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = function StateMachine(Model, settings) {
1616
onleave: function onleave(options) {
1717
debug(`Leaving state: ${options.from}`)
1818
options.instance = options.args[0]
19+
delete options.args[0]
1920
return Model.notifyObserversOf('fsm:onleave', options)
2021
.then(result => Object.assign(options, result))
2122
},
@@ -25,7 +26,7 @@ module.exports = function StateMachine(Model, settings) {
2526
debug(`Finalizing state: ${options.to}`)
2627
return options.instance.updateAttribute(stateProperty, options.to)
2728
.then(result => {
28-
options.res = result
29+
options.instance = result
2930
return Model.notifyObserversOf('fsm:onenter', options)
3031
})
3132
.then(result => Object.assign(options, result))
@@ -35,7 +36,11 @@ module.exports = function StateMachine(Model, settings) {
3536
onentered: function onentered(options) {
3637
debug(`State is now: ${options.to}`)
3738
return Model.notifyObserversOf('fsm:onentered', options)
38-
.then(result => Object.assign(options, result))
39+
.then(result => {
40+
// Return either the instance, or whatever was put in options.res
41+
options.res = options.res || result.instance
42+
return options
43+
})
3944
},
4045
},
4146
}
@@ -103,7 +108,10 @@ module.exports = function StateMachine(Model, settings) {
103108
Model.__FSM_CONFIG__.callbacks[handler] = function runHandler(options) {
104109
debug('Calling %s: %o', handler, options)
105110
return Model.notifyObserversOf(`fsm:${handler}`, options)
106-
.then(result => Object.assign(options, result))
111+
.then(options => {
112+
debug('Result from %s: %o', handler, options)
113+
return options
114+
})
107115
}
108116
})
109117

@@ -113,7 +121,11 @@ module.exports = function StateMachine(Model, settings) {
113121
// Add event methods to the prototype.
114122
Model.getEventNames(settings.events).forEach(eventName => {
115123
Model.prototype[eventName] = function event(...args) {
116-
return Model.app.getStateMachine(this)[eventName](this, ...args)
124+
const fms = Model.app.getStateMachine(this)
125+
126+
return fms[eventName](this, ...args)
127+
.then(result => result)
128+
.finally(() => Model.app.deleteStateMachine(this))
117129
}
118130
})
119131
}
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
const FcSubscription = require('../providers/subscription')
22

33
module.exports = function SubscriptionModel(Subscription) {
4-
Subscription.observe('fsm:oncancel', ctx => new FcSubscription(ctx.instance).cancel().then(() => ctx))
5-
6-
Subscription.observe('fsm:onreactivate', ctx => new FcSubscription(ctx.instance).reactivate().then(() => ctx))
7-
8-
Subscription.observe('fsm:onexpire', ctx => new FcSubscription(ctx.instance).expire().then(() => ctx))
4+
// Subscription.observe('fsm:oncancel', ctx => new FcSubscription(ctx.instance).cancel().then(() => ctx))
5+
//
6+
// Subscription.observe('fsm:onreactivate', ctx => new FcSubscription(ctx.instance).reactivate().then(() => ctx))
7+
//
8+
// Subscription.observe('fsm:onexpire', ctx => new FcSubscription(ctx.instance).expire().then(() => ctx))
99
}

test/test.js

Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ describe('State changes', function() {
5050
it('Should save the final state', function() {
5151
return this.subscription.cancel()
5252
.then(subscription => {
53+
console.log('sdfsdfsdfsdd09-934-93409we09w09r0e9', subscription)
5354
expect(subscription).to.have.property('status', 'canceled')
5455
})
5556
})
@@ -148,8 +149,7 @@ describe('Validation', function() {
148149
})
149150
})
150151

151-
describe('Get event states and names', function() {
152-
152+
describe('Utility', function() {
153153
const testEvents = [
154154
{ name: 'activate', from: [ 'none' ], to: 'active' },
155155
{ name: 'cancel', from: 'active', to: 'canceled' },
@@ -158,20 +158,90 @@ describe('Get event states and names', function() {
158158
{ name: 'expire', from: '*', to: 'expired' },
159159
]
160160

161-
it('should extract the event states', function(done) {
162-
const stateEvents = Subscription.getEventStates(testEvents)
161+
describe('getEventStates', function() {
162+
it('should extract the event states', function() {
163+
const stateEvents = Subscription.getEventStates(testEvents)
163164

164-
expect(stateEvents.length).to.equal(4)
165-
expect(stateEvents).to.include('none', 'active', 'canceled', 'expired')
166-
done()
165+
expect(stateEvents.length).to.equal(4)
166+
expect(stateEvents).to.include('none', 'active', 'canceled', 'expired')
167+
})
167168
})
168169

169-
it('should extract the event names', function(done) {
170-
const stateNames = Subscription.getEventNames(testEvents)
170+
describe('getEventNames', function() {
171+
it('should extract the event names', function() {
172+
const stateNames = Subscription.getEventNames(testEvents)
173+
174+
expect(stateNames.length).to.equal(4)
175+
expect(stateNames).to.include('activate', 'cancel', 'reactivate', 'expire')
176+
})
177+
})
178+
})
179+
180+
describe('Cache', function() {
181+
beforeEach(function() {
182+
delete app.locals['loopback-component-fsm']
183+
})
171184

172-
expect(stateNames.length).to.equal(4)
173-
expect(stateNames).to.include('activate', 'cancel', 'reactivate', 'expire')
174-
done()
185+
describe('Add to cache', function() {
186+
const subscription = new Subscription({
187+
id: 1,
188+
status: 'active',
189+
})
190+
191+
it('should create and cache a new state machine', function() {
192+
const fsm = app.getStateMachine(subscription)
193+
expect(app.locals).to.have.deep.property('loopback-component-fsm.Subscription.id:1', fsm)
194+
})
195+
})
196+
197+
describe('Remove from cache', function() {
198+
const subscription = new Subscription({
199+
id: 1,
200+
status: 'active',
201+
})
202+
203+
it('should delete an existing state machine from the cache', function() {
204+
app.getStateMachine(subscription)
205+
app.deleteStateMachine(subscription)
206+
expect(app.locals).to.not.have.deep.property('loopback-component-fsm.Subscription.id:1')
207+
})
175208
})
176209

210+
describe('Use cache through transition', function() {
211+
beforeEach(function() {
212+
return Subscription.create({ status: 'active' })
213+
.then(subscription => {
214+
this.subscription = subscription
215+
})
216+
})
217+
218+
it('should delete the state machine on completion', function() {
219+
return this.subscription.cancel()
220+
.then(() => {
221+
expect(app.locals).to.not.have.deep.property(`loopback-component-fsm.Subscription.id:${this.subscription.id}`)
222+
})
223+
})
224+
225+
it('should reuse an existing state machine if one is in use', function(done) {
226+
227+
// Make the subscription.cancel method take 200ms second to run.
228+
Subscription.observe('fsm:oncancel', ctx => {
229+
return Promise.delay(200).return(ctx)
230+
})
231+
232+
// Start a cancelation.
233+
this.subscription.cancel()
234+
.then(result => {
235+
expect(app.locals).to.not.have.deep.property(`loopback-component-fsm.Subscription.id:${this.subscription.id}`)
236+
done()
237+
})
238+
239+
// Start another cancel in 100ms (whilst the other is still running).
240+
Promise.delay(100).then(() => this.subscription.cancel())
241+
.then(() => Promise.reject(new Error('Should not get this far')))
242+
.catch(err => {
243+
expect(err).to.have.property('message', 'Previous transition pending')
244+
})
245+
})
246+
})
177247
})

0 commit comments

Comments
 (0)