1 | var RuleEngine = require('../index');
|
2 | var expect = require("chai").expect;
|
3 | describe("Rules", function() {
|
4 | describe(".init()", function() {
|
5 | it("should empty the existing rule array", function() {
|
6 | var rules = [{
|
7 | "condition": function(R) {
|
8 | R.when(1);
|
9 | },
|
10 | "consequence": function(R) {
|
11 | R.stop();
|
12 | }
|
13 | }];
|
14 | var R = new RuleEngine(rules);
|
15 | R.init();
|
16 | expect(R.rules).to.eql([]);
|
17 | });
|
18 | });
|
19 | describe(".register()", function() {
|
20 | it("Rule should be turned on if the field - ON is absent in the rule", function() {
|
21 | var rules = [{
|
22 | "condition": function(R) {
|
23 | R.when(1);
|
24 | },
|
25 | "consequence": function(R) {
|
26 | R.stop();
|
27 | }
|
28 | }];
|
29 | var R = new RuleEngine(rules);
|
30 | expect(R.rules[0].on).to.eql(true);
|
31 | });
|
32 | it("Rule can be passed to register as both arrays and individual objects", function() {
|
33 | var rule = {
|
34 | "condition": function(R) {
|
35 | R.when(1);
|
36 | },
|
37 | "consequence": function(R) {
|
38 | R.stop();
|
39 | }
|
40 | };
|
41 | var R1 = new RuleEngine(rule);
|
42 | var R2 = new RuleEngine([rule]);
|
43 | expect(R1.rules).to.eql(R2.rules);
|
44 | });
|
45 | it("Rules can be appended multiple times via register after creating rule engine instance", function() {
|
46 | var rules = [{
|
47 | "condition": function(R) {
|
48 | R.when(1);
|
49 | },
|
50 | "consequence": function(R) {
|
51 | R.stop();
|
52 | }
|
53 | }, {
|
54 | "condition": function(R) {
|
55 | R.when(0);
|
56 | },
|
57 | "consequence": function(R) {
|
58 | R.stop();
|
59 | }
|
60 | }];
|
61 | var R1 = new RuleEngine(rules);
|
62 | var R2 = new RuleEngine(rules[0]);
|
63 | R2.register(rules[1]);
|
64 | expect(R1.rules).to.eql(R2.rules);
|
65 | });
|
66 | });
|
67 | describe(".sync()", function() {
|
68 | it("should only push active rules into active rules array", function() {
|
69 | var rules = [{
|
70 | "condition": function(R) {
|
71 | R.when(1);
|
72 | },
|
73 | "consequence": function(R) {
|
74 | R.stop();
|
75 | },
|
76 | "id": "one",
|
77 | "on": true
|
78 | }, {
|
79 | "condition": function(R) {
|
80 | R.when(0);
|
81 | },
|
82 | "consequence": function(R) {
|
83 | R.stop();
|
84 | },
|
85 | "id": "one",
|
86 | "on": false
|
87 | }];
|
88 | var R = new RuleEngine();
|
89 | R.register(rules);
|
90 | expect(R.activeRules).not.to.eql(R.rules);
|
91 | });
|
92 | it("should sort the rules accroding to priority, if priority is present", function() {
|
93 | var rules = [{
|
94 | "priority": 8,
|
95 | "index": 1,
|
96 | "condition": function(R) {
|
97 | R.when(1);
|
98 | },
|
99 | "consequence": function(R) {
|
100 | R.stop();
|
101 | },
|
102 | }, {
|
103 | "priority": 6,
|
104 | "index": 2,
|
105 | "condition": function(R) {
|
106 | R.when(1);
|
107 | },
|
108 | "consequence": function(R) {
|
109 | R.stop();
|
110 | },
|
111 | }, {
|
112 | "priority": 9,
|
113 | "index": 0,
|
114 | "condition": function(R) {
|
115 | R.when(1);
|
116 | },
|
117 | "consequence": function(R) {
|
118 | R.stop();
|
119 | },
|
120 | }];
|
121 | var R = new RuleEngine();
|
122 | R.register(rules);
|
123 | expect(R.activeRules[2].index).to.eql(2);
|
124 | });
|
125 | });
|
126 | describe(".exec()", function() {
|
127 | it("should run consequnce when condition matches", function() {
|
128 | var rule = {
|
129 | "condition": function(R) {
|
130 | R.when(this && (this.transactionTotal < 500));
|
131 | },
|
132 | "consequence": function(R) {
|
133 | this.result = false;
|
134 | R.stop();
|
135 | }
|
136 | };
|
137 | var R = new RuleEngine(rule);
|
138 | R.execute({
|
139 | "transactionTotal": 200
|
140 | }, function(result) {
|
141 | expect(result.result).to.eql(false);
|
142 | });
|
143 | });
|
144 | it("should chain rules and find result with next()", function() {
|
145 | var rule = [{
|
146 | "condition": function(R) {
|
147 | R.when(this && (this.card == "VISA"));
|
148 | },
|
149 | "consequence": function(R) {
|
150 | R.stop();
|
151 | this.result = "Custom Result";
|
152 | },
|
153 | "priority": 4
|
154 | }, {
|
155 | "condition": function(R) {
|
156 | R.when(this && (this.transactionTotal < 1000));
|
157 | },
|
158 | "consequence": function(R) {
|
159 | R.next();
|
160 | },
|
161 | "priority": 8
|
162 | }];
|
163 | var R = new RuleEngine(rule);
|
164 | R.execute({
|
165 | "transactionTotal": 200,
|
166 | "card": "VISA"
|
167 | }, function(result) {
|
168 | expect(result.result).to.eql("Custom Result");
|
169 | });
|
170 | });
|
171 | it("should provide access to rule definition properties via rule()", function() {
|
172 | var rule = {
|
173 | "name": "sample rule name",
|
174 | "id": "xyzzy",
|
175 | "condition": function(R) {
|
176 | R.when(this && (this.input === true));
|
177 | },
|
178 | "consequence": function(R) {
|
179 | this.result = true;
|
180 | this.ruleName = R.rule().name;
|
181 | this.ruleID = R.rule().id;
|
182 | R.stop();
|
183 | }
|
184 | };
|
185 | var R = new RuleEngine(rule);
|
186 | R.execute({
|
187 | "input": true
|
188 | }, function(result) {
|
189 | expect(result.ruleName).to.eql(rule.name);
|
190 | expect(result.ruleID).to.eql(rule.id);
|
191 | });
|
192 | });
|
193 | it("should include the matched rule path", function() {
|
194 | var rules = [
|
195 | {
|
196 | "name": "rule A",
|
197 | "condition": function(R) {
|
198 | R.when(this && (this.x === true));
|
199 | },
|
200 | "consequence": function(R) {
|
201 | R.next();
|
202 | }
|
203 | },
|
204 | {
|
205 | "name": "rule B",
|
206 | "condition": function(R) {
|
207 | R.when(this && (this.y === true));
|
208 | },
|
209 | "consequence": function(R) {
|
210 | R.next();
|
211 | }
|
212 | },
|
213 | {
|
214 | "id": "rule C",
|
215 | "condition": function(R) {
|
216 | R.when(this && (this.x === true && this.y === false));
|
217 | },
|
218 | "consequence": function(R) {
|
219 | R.next();
|
220 | }
|
221 | },
|
222 | {
|
223 | "id": "rule D",
|
224 | "condition": function(R) {
|
225 | R.when(this && (this.x === false && this.y === false));
|
226 | },
|
227 | "consequence": function(R) {
|
228 | R.next();
|
229 | }
|
230 | },
|
231 | {
|
232 | "condition": function(R) {
|
233 | R.when(this && (this.x === true && this.y === false));
|
234 | },
|
235 | "consequence": function(R) {
|
236 | R.next();
|
237 | }
|
238 | }
|
239 | ];
|
240 | var lastMatch = 'index_' + ((rules.length)-1).toString();
|
241 | var R = new RuleEngine(rules);
|
242 | R.execute({
|
243 | "x": true,
|
244 | "y": false
|
245 | }, function(result) {
|
246 | expect(result.matchPath).to.eql([rules[0].name, rules[2].id, lastMatch]);
|
247 | });
|
248 | });
|
249 |
|
250 | it("should support fact as optional second parameter for es6 compatibility", function() {
|
251 | var rule = {
|
252 | "condition": function(R, fact) {
|
253 | R.when(fact && (fact.transactionTotal < 500));
|
254 | },
|
255 | "consequence": function(R, fact) {
|
256 | fact.result = false;
|
257 | R.stop();
|
258 | }
|
259 | };
|
260 | var R = new RuleEngine(rule);
|
261 | R.execute({
|
262 | "transactionTotal": 200
|
263 | }, function(result) {
|
264 | expect(result.result).to.eql(false);
|
265 | });
|
266 | });
|
267 |
|
268 | });
|
269 | describe(".findRules()", function() {
|
270 | var rules = [{
|
271 | "condition": function(R) {
|
272 | R.when(1);
|
273 | },
|
274 | "consequence": function(R) {
|
275 | R.stop();
|
276 | },
|
277 | "id": "one"
|
278 | }, {
|
279 | "condition": function(R) {
|
280 | R.when(0);
|
281 | },
|
282 | "consequence": function(R) {
|
283 | R.stop();
|
284 | },
|
285 | "id": "two"
|
286 | }];
|
287 | var R = new RuleEngine(rules);
|
288 | it("find selector function for rules should exact number of matches", function() {
|
289 | expect(R.findRules({
|
290 | "id": "one"
|
291 | }).length).to.eql(1);
|
292 | });
|
293 | it("find selector function for rules should give the correct match as result", function() {
|
294 | expect(R.findRules({
|
295 | "id": "one"
|
296 | })[0].id).to.eql("one");
|
297 | });
|
298 | it("find without condition works fine", function() {
|
299 | expect(R.findRules().length).to.eql(2);
|
300 | });
|
301 | });
|
302 | describe(".turn()", function() {
|
303 | var rules = [{
|
304 | "condition": function(R) {
|
305 | R.when(1);
|
306 | },
|
307 | "consequence": function(R) {
|
308 | R.stop();
|
309 | },
|
310 | "id": "one"
|
311 | }, {
|
312 | "condition": function(R) {
|
313 | R.when(0);
|
314 | },
|
315 | "consequence": function(R) {
|
316 | R.stop();
|
317 | },
|
318 | "id": "two",
|
319 | "on": false
|
320 | }];
|
321 | var R = new RuleEngine(rules);
|
322 | it("checking whether turn off rules work as expected", function() {
|
323 | R.turn("OFF", {
|
324 | "id": "one"
|
325 | });
|
326 | expect(R.findRules({
|
327 | "id": "one"
|
328 | })[0].on).to.eql(false);
|
329 | });
|
330 | it("checking whether turn on rules work as expected", function() {
|
331 | R.turn("ON", {
|
332 | "id": "two"
|
333 | });
|
334 | expect(R.findRules({
|
335 | "id": "two"
|
336 | })[0].on).to.eql(true);
|
337 | });
|
338 | });
|
339 | describe(".prioritize()", function() {
|
340 | var rules = [{
|
341 | "condition": function(R) {
|
342 | R.when(1);
|
343 | },
|
344 | "consequence": function(R) {
|
345 | R.stop();
|
346 | },
|
347 | "id": "two",
|
348 | "priority": 1
|
349 | }, {
|
350 | "condition": function(R) {
|
351 | R.when(0);
|
352 | },
|
353 | "consequence": function(R) {
|
354 | R.stop();
|
355 | },
|
356 | "id": "zero",
|
357 | "priority": 8
|
358 | }, {
|
359 | "condition": function(R) {
|
360 | R.when(0);
|
361 | },
|
362 | "consequence": function(R) {
|
363 | R.stop();
|
364 | },
|
365 | "id": "one",
|
366 | "priority": 4
|
367 | }];
|
368 | var R = new RuleEngine(rules);
|
369 | it("checking whether prioritize work", function() {
|
370 | R.prioritize(10, {
|
371 | "id": "one"
|
372 | });
|
373 | expect(R.findRules({
|
374 | "id": "one"
|
375 | })[0].priority).to.eql(10);
|
376 | });
|
377 | it("checking whether rules reorder after prioritize", function() {
|
378 | R.prioritize(10, {
|
379 | "id": "one"
|
380 | });
|
381 | expect(R.activeRules[0].id).to.eql("one");
|
382 | });
|
383 | });
|
384 | describe("ignoreFactChanges", function() {
|
385 | var rules = [{
|
386 | "name": "rule1",
|
387 | "condition": function(R) {
|
388 | R.when(this.value1 > 5);
|
389 | },
|
390 | "consequence": function(R) {
|
391 | this.result = false;
|
392 | this.errors = this.errors || [];
|
393 | this.errors.push('must be less than 5');
|
394 | R.next();
|
395 | }
|
396 | }];
|
397 |
|
398 | var fact = {
|
399 | "value1": 6
|
400 | };
|
401 |
|
402 | it("doesn't rerun when a fact changes if ignoreFactChanges is true", function(done) {
|
403 | var R = new RuleEngine(rules, { ignoreFactChanges: true });
|
404 |
|
405 | R.execute(fact, function(result) {
|
406 | expect(result.errors).to.have.length(1);
|
407 | done();
|
408 | });
|
409 | });
|
410 | });
|
411 | });
|