Skip to content

Commit 3c1c8b7

Browse files
author
Cache Hamm
committed
fix bug where a rule would emitting succes/failure multiple times
1 parent e2347d4 commit 3c1c8b7

2 files changed

Lines changed: 60 additions & 39 deletions

File tree

src/rule.js

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,6 @@ class Rule extends EventEmitter {
161161
}
162162
}
163163
condition.result = passes
164-
if (passes) {
165-
this.emit('success', this.event, almanac, ruleResult)
166-
} else {
167-
this.emit('failure', this.event, almanac, ruleResult)
168-
}
169164
return passes
170165
}
171166

@@ -245,14 +240,23 @@ class Rule extends EventEmitter {
245240
return prioritizeAndRun(conditions, 'all')
246241
}
247242

248-
if (ruleResult.conditions.any) {
249-
let result = await any(ruleResult.conditions.any)
243+
/**
244+
* Emits based on rule evaluation result, and decorates ruleResult with 'result' property
245+
* @param {Boolean} result
246+
*/
247+
let processResult = (result) => {
250248
ruleResult.result = result
249+
if (result) this.emit('success', ruleResult.event, almanac, ruleResult)
250+
else this.emit('failure', ruleResult.event, almanac, ruleResult)
251251
return ruleResult
252+
}
253+
254+
if (ruleResult.conditions.any) {
255+
let result = await any(ruleResult.conditions.any)
256+
return processResult(result)
252257
} else {
253258
let result = await all(ruleResult.conditions.all)
254-
ruleResult.result = result
255-
return ruleResult
259+
return processResult(result)
256260
}
257261
}
258262
}

test/engine-event.test.js

Lines changed: 47 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,28 +18,39 @@ describe('Engine: event', () => {
1818
fact: 'age',
1919
operator: 'greaterThanInclusive',
2020
value: 21
21+
}, {
22+
fact: 'qualified',
23+
operator: 'equal',
24+
value: true
2125
}]
2226
}
2327
beforeEach(() => {
2428
engine = engineFactory()
2529
let ruleOptions = { conditions, event, priority: 100 }
2630
let determineDrinkingAgeRule = factories.rule(ruleOptions)
2731
engine.addRule(determineDrinkingAgeRule)
32+
// age will succeed because 21 >= 21
2833
engine.addFact('age', 21)
34+
// set 'qualified' to fail. rule will succeed because of 'any'
35+
engine.addFact('qualified', false)
2936
})
3037

31-
describe('engine events', () => {
32-
it('passes the event type and params', (done) => {
33-
engine.on('success', function (a, almanac, ruleResult) {
34-
try {
35-
expect(a).to.eql(event)
36-
expect(almanac).to.be.an.instanceof(Almanac)
37-
expect(ruleResult.result).to.be.true()
38-
expect(ruleResult.conditions.any[0].result).to.be.true()
39-
} catch (e) { return done(e) }
40-
done()
38+
context('engine events', () => {
39+
it('passes the event type and params', async () => {
40+
let failureSpy = sinon.spy()
41+
let successSpy = sinon.spy()
42+
engine.on('success', function (e, almanac, ruleResult) {
43+
expect(e).to.eql(event)
44+
expect(almanac).to.be.an.instanceof(Almanac)
45+
expect(ruleResult.result).to.be.true()
46+
expect(ruleResult.conditions.any[0].result).to.be.true()
47+
expect(ruleResult.conditions.any[1].result).to.be.false()
48+
successSpy()
4149
})
42-
engine.run()
50+
engine.on('failure', failureSpy)
51+
await engine.run()
52+
expect(failureSpy.callCount).to.equal(0)
53+
expect(successSpy.callCount).to.equal(1)
4354
})
4455

4556
it('emits using the event "type"', (done) => {
@@ -89,38 +100,44 @@ describe('Engine: event', () => {
89100
})
90101
})
91102
})
92-
describe('rule events', () => {
93-
it('on-success, it passes the event type and params', (done) => {
103+
104+
context('rule events', () => {
105+
it('on-success, it passes the event type and params', async () => {
94106
let failureSpy = sinon.spy()
107+
let successSpy = sinon.spy()
95108
let rule = engine.rules[0]
96109
rule.on('success', function (e, almanac, ruleResult) {
97-
try {
98-
expect(e).to.eql(event)
99-
expect(almanac).to.be.an.instanceof(Almanac)
100-
expect(failureSpy.callCount).to.equal(0)
101-
expect(ruleResult.conditions.any[0].result).to.be.true()
102-
} catch (err) { return done(err) }
103-
done()
110+
expect(e).to.eql(event)
111+
expect(almanac).to.be.an.instanceof(Almanac)
112+
expect(failureSpy.callCount).to.equal(0)
113+
expect(ruleResult.result).to.be.true()
114+
expect(ruleResult.conditions.any[0].result).to.be.true()
115+
expect(ruleResult.conditions.any[1].result).to.be.false()
116+
successSpy()
104117
})
105118
rule.on('failure', failureSpy)
106-
engine.run()
119+
await engine.run()
120+
expect(successSpy.callCount).to.equal(1)
107121
})
108122

109-
it('on-failure, it passes the event type and params', (done) => {
123+
it('on-failure, it passes the event type and params', async () => {
110124
let successSpy = sinon.spy()
125+
let failureSpy = sinon.spy()
111126
let rule = engine.rules[0]
112127
rule.on('failure', function (e, almanac, ruleResult) {
113-
try {
114-
expect(e).to.eql(event)
115-
expect(almanac).to.be.an.instanceof(Almanac)
116-
expect(successSpy.callCount).to.equal(0)
117-
expect(ruleResult.conditions.any[0].result).to.be.false()
118-
} catch (err) { return done(err) }
119-
done()
128+
expect(e).to.eql(event)
129+
expect(almanac).to.be.an.instanceof(Almanac)
130+
expect(successSpy.callCount).to.equal(0)
131+
expect(ruleResult.result).to.be.false()
132+
expect(ruleResult.conditions.any[0].result).to.be.false()
133+
expect(ruleResult.conditions.any[1].result).to.be.false()
134+
failureSpy()
120135
})
121136
rule.on('success', successSpy)
137+
// both conditions will fail
122138
engine.addFact('age', 10)
123-
engine.run()
139+
await engine.run()
140+
expect(failureSpy.callCount).to.equal(1)
124141
})
125142
})
126143
})

0 commit comments

Comments
 (0)