Skip to content

Commit e18aed4

Browse files
author
Cache Hamm
committed
Add acceptance tests
1 parent b4bfae1 commit e18aed4

6 files changed

Lines changed: 180 additions & 4 deletions

File tree

docs/rules.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ For an example, see [fact-dependency](../examples/04-fact-dependency.js)
219219

220220
### Comparing facts
221221

222-
Sometimes it is necessary to compare facts against others facts. This can be accomplished by nesting the second fact within the `value` property. This second fact has access to the same `params` and `path` helpers as the primary fact.
222+
Sometimes it is necessary to compare facts against other facts. This can be accomplished by nesting the second fact within the `value` property. This second fact has access to the same `params` and `path` helpers as the primary fact.
223223

224224
```js
225225
// identifies whether the current widget price is above a maximum

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
],
4949
"file": "./test/support/bootstrap.js",
5050
"checkLeaks": true,
51+
"recursive": true,
5152
"globals": [
5253
"expect"
5354
]

src/almanac.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export default class Almanac {
114114
.then(factValue => {
115115
if (isObjectLike(factValue)) {
116116
const pathValue = JSONPath({ path, json: factValue, wrap: false })
117-
debug(`condition::evaluate extracting object property ${path}, received: ${pathValue}`)
117+
debug(`condition::evaluate extracting object property ${path}, received: ${JSON.stringify(pathValue)}`)
118118
return pathValue
119119
} else {
120120
debug(`condition::evaluate could not compute object path(${path}) of non-object: ${factValue} <${typeof factValue}>; continuing with ${factValue}`)

src/condition.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ export default class Condition {
101101
return almanac.factValue(this.fact, this.params, this.path)
102102
.then(leftHandSideValue => {
103103
const result = op.evaluate(leftHandSideValue, rightHandSideValue)
104-
debug(`condition::evaluate <${leftHandSideValue} ${this.operator} ${rightHandSideValue}?> (${result})`)
104+
debug(`condition::evaluate <${JSON.stringify(leftHandSideValue)} ${this.operator} ${JSON.stringify(rightHandSideValue)}?> (${result})`)
105105
return { result, leftHandSideValue, rightHandSideValue, operator: this.operator }
106106
})
107107
})

src/engine.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,6 @@ class Engine extends EventEmitter {
212212
*/
213213
run (runtimeFacts = {}) {
214214
debug('engine::run started')
215-
debug('engine::run runtimeFacts:', runtimeFacts)
216215
runtimeFacts['success-events'] = new Fact('success-events', SuccessEventFact(), { cache: false })
217216
this.status = RUNNING
218217
const almanac = new Almanac(this.facts, runtimeFacts, { allowUndefinedFacts: this.allowUndefinedFacts })

test/acceptance/acceptance.js

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
'use strict'
2+
3+
import sinon from 'sinon'
4+
import { expect } from 'chai'
5+
import { Engine } from '../../src/index'
6+
7+
/**
8+
* acceptance tests are intended to use features that, when used in combination,
9+
* could cause integration bugs not caught by the rest of the test suite
10+
*/
11+
describe('Acceptance', () => {
12+
let sandbox
13+
before(() => {
14+
sandbox = sinon.createSandbox()
15+
})
16+
afterEach(() => {
17+
sandbox.restore()
18+
})
19+
const factParam = 1
20+
const event1 = {
21+
type: 'event-1',
22+
params: {
23+
eventParam: 1
24+
}
25+
}
26+
const event2 = {
27+
type: 'event-2'
28+
}
29+
const expectedFirstRuleResult = {
30+
all: [{
31+
fact: 'high-priority',
32+
params: {
33+
factParam
34+
},
35+
operator: 'contains',
36+
path: '$.values',
37+
value: 2,
38+
factResult: [2],
39+
result: true
40+
},
41+
{
42+
fact: 'low-priority',
43+
operator: 'in',
44+
value: [2],
45+
factResult: 2,
46+
result: true
47+
}
48+
],
49+
operator: 'all',
50+
priority: 1
51+
}
52+
let successSpy
53+
let failureSpy
54+
let highPrioritySpy
55+
let lowPrioritySpy
56+
57+
function delay (value) {
58+
return new Promise(resolve => setTimeout(() => resolve(value), 5))
59+
}
60+
61+
function setup (options = {}) {
62+
const engine = new Engine()
63+
highPrioritySpy = sandbox.spy()
64+
lowPrioritySpy = sandbox.spy()
65+
66+
engine.addRule({
67+
name: 'first',
68+
priority: 10,
69+
conditions: {
70+
all: [{
71+
fact: 'high-priority',
72+
params: {
73+
factParam
74+
},
75+
operator: 'contains',
76+
path: '$.values',
77+
value: options.highPriorityValue
78+
}, {
79+
fact: 'low-priority',
80+
operator: 'in',
81+
value: options.lowPriorityValue
82+
}]
83+
},
84+
event: event1,
85+
onSuccess: async (event, almanac, ruleResults) => {
86+
expect(ruleResults.name).to.equal('first')
87+
expect(ruleResults.event).to.deep.equal(event1)
88+
expect(ruleResults.priority).to.equal(10)
89+
expect(ruleResults.conditions).to.deep.equal(expectedFirstRuleResult)
90+
91+
return delay(almanac.addRuntimeFact('rule-created-fact', { array: options.highPriorityValue }))
92+
}
93+
})
94+
95+
engine.addRule({
96+
name: 'second',
97+
priority: 1,
98+
conditions: {
99+
all: [{
100+
fact: 'high-priority',
101+
params: {
102+
factParam
103+
},
104+
operator: 'containsDivisibleValuesOf',
105+
path: '$.values',
106+
value: {
107+
fact: 'rule-created-fact',
108+
path: '$.array' // set by 'success' of first rule
109+
}
110+
}]
111+
},
112+
event: event2
113+
})
114+
115+
engine.addOperator('containsDivisibleValuesOf', (factValue, jsonValue) => {
116+
return factValue.some(v => v % jsonValue === 0)
117+
})
118+
119+
engine.addFact('high-priority', async function (params, almanac) {
120+
highPrioritySpy(params)
121+
const idx = await almanac.factValue('sub-fact')
122+
return delay({ values: [idx + params.factParam] }) // { values: [baseIndex + factParam] }
123+
}, { priority: 2 })
124+
125+
engine.addFact('low-priority', async function (params, almanac) {
126+
lowPrioritySpy(params)
127+
const idx = await almanac.factValue('sub-fact')
128+
return delay(idx + 1) // baseIndex + 1
129+
}, { priority: 1 })
130+
131+
engine.addFact('sub-fact', async function (params, almanac) {
132+
const baseIndex = await almanac.factValue('baseIndex')
133+
return delay(baseIndex)
134+
})
135+
successSpy = sandbox.spy()
136+
failureSpy = sandbox.spy()
137+
engine.on('success', successSpy)
138+
engine.on('failure', failureSpy)
139+
140+
return engine
141+
}
142+
143+
it('succeeds', async () => {
144+
const engine = setup({
145+
highPriorityValue: 2,
146+
lowPriorityValue: [2]
147+
})
148+
149+
const engineResult = await engine.run({ baseIndex: 1 })
150+
151+
expect(engineResult.events.length).to.equal(2)
152+
expect(engineResult.events[0]).to.deep.equal(event1)
153+
expect(engineResult.events[1]).to.deep.equal(event2)
154+
expect(successSpy).to.have.been.calledTwice()
155+
expect(successSpy).to.have.been.calledWith(event1)
156+
expect(successSpy).to.have.been.calledWith(event2)
157+
expect(highPrioritySpy).to.have.been.calledBefore(lowPrioritySpy)
158+
expect(failureSpy).to.not.have.been.called()
159+
})
160+
161+
it('fails', async () => {
162+
const engine = setup({
163+
highPriorityValue: 2,
164+
lowPriorityValue: [3] // falsey
165+
})
166+
167+
const engineResult = await engine.run({ baseIndex: 1, 'rule-created-fact': '' })
168+
169+
expect(engineResult.events.length).to.equal(0)
170+
expect(failureSpy).to.have.been.calledTwice()
171+
expect(failureSpy).to.have.been.calledWith(event1)
172+
expect(failureSpy).to.have.been.calledWith(event2)
173+
expect(highPrioritySpy).to.have.been.calledBefore(lowPrioritySpy)
174+
expect(successSpy).to.not.have.been.called()
175+
})
176+
})

0 commit comments

Comments
 (0)