From acd5d6147681f090895494206606d58343b8d241 Mon Sep 17 00:00:00 2001 From: Frank Thelen Date: Sun, 21 Jan 2018 02:00:37 +0100 Subject: [PATCH] =?UTF-8?q?import=20lodash=20//=20new=20Rule()=20//=20expo?= =?UTF-8?q?sing=20{=C2=A0Rools,=20Rule=20}=20//=20new=20feature=20`Rule.ex?= =?UTF-8?q?tend`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 113 ++++++++++++++++------------ package-lock.json | 8 +- package.json | 4 +- src/Rools.js | 4 +- src/Rule.js | 47 +++++++++--- src/RuleSet.js | 21 +++++- src/index.js | 3 +- src/utils.js | 8 ++ test/Rule.spec.js | 148 +++++++++++++++++++++++++++++++++++++ test/async.spec.js | 2 +- test/classes.spec.js | 10 +-- test/errors.spec.js | 12 +-- test/extend.spec.js | 98 ++++++++++++++++++++++++ test/final.spec.js | 6 +- test/logging.spec.js | 22 +++--- test/longer.spec.js | 42 +++++------ test/premises.spec.js | 66 ++++++++--------- test/priority.spec.js | 38 ++++++---- test/reevaluate.spec.js | 26 +++---- test/refraction.spec.js | 10 +-- test/register.spec.js | 74 ------------------- test/result.spec.js | 2 +- test/rules/availability.js | 10 ++- test/rules/mood.js | 18 +++-- test/simple.spec.js | 2 +- test/specificity.spec.js | 24 +++--- test/strategy.spec.js | 32 ++++---- test/withdraw.spec.js | 10 +-- 28 files changed, 555 insertions(+), 305 deletions(-) create mode 100644 src/utils.js create mode 100644 test/Rule.spec.js create mode 100644 test/extend.spec.js delete mode 100644 test/register.spec.js diff --git a/README.md b/README.md index bd84a09..e87f007 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # rools -This is a small rule engine for Node. +A small rule engine for Node. [![build status](https://img.shields.io/travis/frankthelen/rools.svg)](http://travis-ci.org/frankthelen/rools) [![Coverage Status](https://coveralls.io/repos/github/frankthelen/rools/badge.svg?branch=master)](https://coveralls.io/github/frankthelen/rools?branch=master) @@ -32,8 +32,8 @@ npm install --save rools This is a basic example. ```javascript -// import Rools -const Rools = require('rools'); +// import +const { Rools, Rule } = require('rools'); // facts const facts = { @@ -49,14 +49,14 @@ const facts = { }; // rules -const ruleMoodGreat = { +const ruleMoodGreat = new Rule({ name: 'mood is great if 200 stars or more', when: facts => facts.user.stars >= 200, then: (facts) => { facts.user.mood = 'great'; }, -}; -const ruleGoWalking = { +}); +const ruleGoWalking = new Rule({ name: 'go for a walk if mood is great and the weather is fine', when: [ facts => facts.user.mood === 'great', @@ -66,7 +66,7 @@ const ruleGoWalking = { then: (facts) => { facts.goWalking = true; }, -}; +}); // evaluation const rools = new Rools(); @@ -91,7 +91,8 @@ The engine does forward-chaining and works in the usual match-resolve-act cycle. Facts are plain JavaScript or JSON objects or objects from ES6 classes with getters and setters. -Rules are specified in pure JavaScript, i.e., they have premises (`when`) and actions (`then`). +Rules are specified in pure JavaScript via `new Rule()`. +They have premises (`when`) and actions (`then`). Both are JavaScript functions, i.e., classic functions or ES6 arrow functions. Actions can also be asynchronous. @@ -120,18 +121,18 @@ actions (`then`) can be synchronous or asynchronous. Example: asynchronous action using async/await ```javascript -const rule = { +const rule = new Rule({ name: 'check availability', when: facts => facts.user.address.country === 'germany', then: async (facts) => { facts.products = await availabilityCheck(facts.user.address); }, -}; +}); ``` Example: asynchronous action using promises ```javascript -const rule = { +const rule = new Rule({ name: 'check availability', when: facts => facts.user.address.country === 'germany', then: facts => @@ -139,7 +140,7 @@ const rule = { .then((result) => { facts.products = result; }), -}; +}); ``` ### Optimization I @@ -152,38 +153,38 @@ Both options are working fine. Example 1: by reference ```javascript const isApplicable = facts => facts.user.salery >= 2000; -const rule1 = { +const rule1 = new Rule({ when: [ isApplicable, ... ], ... -}; -const rule2 = { +}); +const rule2 = new Rule({ when: [ isApplicable, ... ], ... -}; +}); ``` Example 2: repeat premise ```javascript -const rule1 = { +const rule1 = new Rule({ when: [ facts => facts.user.salery >= 2000, ... ], ... -}; -const rule2 = { +}); +const rule2 = new Rule({ when: [ facts => facts.user.salery >= 2000, ... ], ... -}; +}); ``` Furthermore, it is recommended to de-compose premises with AND relations (`&&`). @@ -191,18 +192,18 @@ For example: ```javascript // this version works... -const rule = { +const rule = new Rule({ when: facts => facts.user.salery >= 2000 && facts.user.age > 25, ... -}; +}); // however, it's better to write it like this... -const rule = { +const rule = new Rule({ when: [ facts => facts.user.salery >= 2000, facts => facts.user.age > 25, ], ... -}; +}); ``` One last thing. Look at the example below. @@ -212,15 +213,15 @@ Later on, at evaluation time (`evaluate()`), both rules are clearly identical. ```javascript let value = 2000; -const rule1 = { +const rule1 = new Rule({ when: facts => facts.user.salery >= value, ... -}; +}); value = 3000; -const rule2 = { +const rule2 = new Rule({ when: facts => facts.user.salery >= value, ... -}; +}); ``` *TL;DR* -- Technically, this is achieved by hashing the premise functions (remember, functions are "first-class" objects in JavaScript). This can be a classic function or an ES6 arrow function; it can be a reference or the function directly. @@ -260,14 +261,14 @@ You usually do this once for a given set of rules. Example: ```javascript -const Rools = require('rools'); +const { Rools } = require('rools'); const rools = new Rools(); ... ``` ### Register rules: `register()` -Rules are plain JavaScript objects with the following properties: +Rules are created through `new Rule()` with the following properties: | Property | Required | Default | Description | |-------------|----------|---------|-------------| @@ -291,14 +292,15 @@ If this happens, the affected Rools instance is inconsistent and should no longe Example: ```javascript -const ruleMoodGreat = { +const { Rools, Rule } = require('rools'); +const ruleMoodGreat = new Rule({ name: 'mood is great if 200 stars or more', when: facts => facts.user.stars >= 200, then: (facts) => { facts.user.mood = 'great'; }, -}; -const ruleGoWalking = { +}); +const ruleGoWalking = new Rule({ name: 'go for a walk if mood is great and the weather is fine', when: [ facts => facts.user.mood === 'great', @@ -308,7 +310,7 @@ const ruleGoWalking = { then: (facts) => { facts.goWalking = true; }, -}; +}); const rools = new Rools(); await rools.register([ruleMoodGreat, ruleGoWalking]); ``` @@ -366,31 +368,48 @@ const rools = new Rools({ ### Version 1.x.x to Version 2.x.x -There are two breaking changes that require some little changes to your code. +There are a few breaking changes that require changes to your code. -`register()` takes the rules to register as an array now. -Reason is to allow a second options parameter in future releases. +Rools exposes now two classes, `Rools` and `Rule`. -Version 1.x.x ```javascript -await register(rule1, rule2, rule3); +// Version 1.x.x +const Rools = require('rools'); +// Version 2.x.x +const { Rools, Rule } = require('rools'); ``` -Version 2.x.x +Rules must now be created with `new Rule()`. + ```javascript -await register([rule1, rule2, rule3]); +// Version 1.x.x +const rule = { + name: 'my rule', + ... +}; +// Version 2.x.x +const rule = new Rule({ + name: 'my rule', + ... +}); ``` -`evaluate()` does not return the facts which was only for convenience anyway. -Instead, it returns an object with some useful information about what it was actually doing. `updated` lists the names of the fact segments that were actually updated during evaluation. `fired` is number of rules that were fired. `elapsed` is the number of milliseconds needed. +`register()` takes the rules to register as an array now. +Reason is to allow a second options parameter for future releases. -Version 1.x.x ```javascript -const facts = await evaluate({ user, weather }); +// Version 1.x.x +await register(rule1, rule2, rule3); +// Version 2.x.x +await register([rule1, rule2, rule3]); ``` -Version 2.x.x +`evaluate()` does not return the facts anymore - which was only for convenience anyway. Instead, it returns an object with some useful information about what it was actually doing. `updated` lists the names of the fact segments that were actually updated during evaluation. `fired` is the number of rules that were fired. `elapsed` is the number of milliseconds needed. + ```javascript +// Version 1.x.x +const facts = await evaluate({ user, weather }); +// Version 2.x.x const { updated, fired, elapsed } = await evaluate({ user, weather }); -console.log(updated, fired, elapsed); // e.g., ["user"] 26 536 +console.log(updated, fired, elapsed); // e.g., ["user"] 26 187 ``` diff --git a/package-lock.json b/package-lock.json index f1db986..1c3beb8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1277,8 +1277,7 @@ "lodash": { "version": "4.17.4", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", - "dev": true + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, "lodash.cond": { "version": "4.5.2", @@ -1292,11 +1291,6 @@ "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", "dev": true }, - "lodash.intersection": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.intersection/-/lodash.intersection-4.4.0.tgz", - "integrity": "sha1-ChG6Yx0OlcI8fy9Mu5ppLtF45wU=" - }, "log-driver": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.5.tgz", diff --git a/package.json b/package.json index f012f29..c718191 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "rools", "version": "1.2.0", - "description": "This is a small rule engine for Node.", + "description": "A small rule engine for Node.", "main": "src/index.js", "author": "Frank Thelen", "license": "MIT", @@ -43,7 +43,7 @@ }, "dependencies": { "bluebird": "^3.5.1", - "lodash.intersection": "^4.4.0", + "lodash": "^4.17.4", "md5": "^2.2.1", "uniqueid": "^1.0.0" } diff --git a/src/Rools.js b/src/Rools.js index 5213478..0b6c45b 100644 --- a/src/Rools.js +++ b/src/Rools.js @@ -1,5 +1,5 @@ +const _ = require('lodash'); const assert = require('assert'); -const intersection = require('lodash.intersection'); const Promise = require('bluebird'); const RuleSet = require('./RuleSet'); const Logger = require('./Logger'); @@ -160,7 +160,7 @@ class Rools { resolveBySpecificity(actions) { const isMoreSpecific = (action, rhs) => action.premises.length > rhs.premises.length && - intersection(action.premises, rhs.premises).length === rhs.premises.length; + _.intersection(action.premises, rhs.premises).length === rhs.premises.length; const isMostSpecific = (action, all) => all.reduce((acc, other) => acc && !isMoreSpecific(other, action), true); const selected = actions.filter(action => isMostSpecific(action, actions)); diff --git a/src/Rule.js b/src/Rule.js index 8006864..0d7a123 100644 --- a/src/Rule.js +++ b/src/Rule.js @@ -1,29 +1,56 @@ +const _ = require('lodash'); const assert = require('assert'); +const { arrify } = require('./utils'); class Rule { constructor({ - name, when, then, priority = 0, final = false, + name, when, then, priority = 0, final = false, extend, }) { this.name = name; - this.when = Array.isArray(when) ? when : [when]; + this.when = arrify(when); this.then = then; this.priority = priority; this.final = final; + this.extend = arrify(extend); this.assert(); } assert() { - const isFunc = func => typeof func === 'function'; - assert(this.name, 'rule "name" is required'); - assert(this.when, `rule "when" is required: "${this.name}"`); - assert(this.then, `rule "then" is required: "${this.name}"`); assert( - !this.when.filter(premise => !isFunc(premise)).length, - `rule "when" must be a function or an array of functions: "${this.name}"`, + this.name, + '"name" is required', ); assert( - isFunc(this.then), - `rule "then" must be a function: "${this.name}"`, + _.isString(this.name), + '"name" must be a string', + ); + assert( + this.when.length, + '"when" is required with at least one premise', + ); + assert( + this.when.reduce((acc, premise) => acc && _.isFunction(premise), true), + '"when" must be a function or an array of functions', + ); + assert( + this.then, + '"then" is required', + ); + assert( + _.isFunction(this.then), + '"then" must be a function', + ); + assert( + _.isInteger(this.priority), + '"priority" must be an integer', + ); + assert( + _.isBoolean(this.final), + '"final" must be a boolean', + ); + assert( + this.extend.reduce((acc, rule) => acc && (rule instanceof Rule), true), + '"extend" must be a Rule or an array of Rules', ); } } diff --git a/src/RuleSet.js b/src/RuleSet.js index abfa92f..2a54551 100644 --- a/src/RuleSet.js +++ b/src/RuleSet.js @@ -1,8 +1,9 @@ +const assert = require('assert'); const md5 = require('md5'); const uniqueid = require('uniqueid'); -const Rule = require('./Rule'); const Action = require('./Action'); const Premise = require('./Premise'); +const Rule = require('./Rule'); class RuleSet { constructor() { @@ -13,14 +14,26 @@ class RuleSet { this.nextPremiseId = uniqueid('p'); } - register(r) { - const rule = new Rule(r); + register(rule) { + assert(rule instanceof Rule, 'rule must be an instance of "Rule"'); + // action const action = new Action({ ...rule, id: this.nextActionId(), }); this.actions.push(action); - rule.when.forEach((when, index) => { + // extend + const walked = new Set(); // cycle check + const whens = new Set(); + const walker = (node) => { + if (walked.has(node)) return; // cycle + walked.add(node); + node.when.forEach((w) => { whens.add(w); }); + node.extend.forEach((r) => { walker(r); }); // recursion + }; + walker(rule); + // premises + [...whens].forEach((when, index) => { const hash = md5(when); // is function already introduced by other rule? let premise = this.premisesByHash[hash]; if (!premise) { // create new premise diff --git a/src/index.js b/src/index.js index e76a5e0..b3ef07a 100644 --- a/src/index.js +++ b/src/index.js @@ -1,3 +1,4 @@ const Rools = require('./Rools'); +const Rule = require('./Rule'); -module.exports = Rools; +module.exports = { Rools, Rule }; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..87f9aed --- /dev/null +++ b/src/utils.js @@ -0,0 +1,8 @@ +const arrify = (obj) => { + if (Array.isArray(obj)) { + return obj; + } + return obj === undefined ? [] : [obj]; +}; + +module.exports = { arrify }; diff --git a/test/Rule.spec.js b/test/Rule.spec.js new file mode 100644 index 0000000..6694eb3 --- /dev/null +++ b/test/Rule.spec.js @@ -0,0 +1,148 @@ +const assert = require('assert'); +const { Rule } = require('..'); +require('./setup'); + +/* eslint-disable no-unused-vars */ + +describe('new Rule()', () => { + it('should not fail if properties are correct / minimum', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: () => true, + then: () => {}, + }); + } catch (error) { + assert.fail(error); + } + }); + + it('should not fail if properties are correct / maximum', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: () => true, + then: () => {}, + final: true, + extend: [new Rule({ name: 'blub', when: () => false, then: () => {} })], + }); + } catch (error) { + assert.fail(error); + } + }); + + it('should fail if rule has no "name"', async () => { + try { + const rule = new Rule({ + when: () => true, + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if "name" is not a string', async () => { + try { + const rule = new Rule({ + name: () => {}, + when: () => true, + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule has no "when"', async () => { + try { + const rule = new Rule({ + name: 'bla', + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule has no "then"', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: () => true, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule "when" is empty', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: [], + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule "when" is neither function nor array', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: 'not a function', + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule "when" is an array with a non-function element', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: ['not a function'], + then: () => {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule "then" is not a function', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: () => true, + then: 'not a function', + }); + assert.fail(); + } catch (error) { + // correct! + } + }); + + it('should fail if rule "extend" contains not a Rule', async () => { + try { + const rule = new Rule({ + name: 'bla', + when: () => true, + then: () => {}, + extend: {}, + }); + assert.fail(); + } catch (error) { + // correct! + } + }); +}); diff --git a/test/async.spec.js b/test/async.spec.js index 4f388b2..ee9e25f 100644 --- a/test/async.spec.js +++ b/test/async.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools } = require('..'); const { frank } = require('./facts/users')(); const { rule1, rule2 } = require('./rules/availability'); require('./setup'); diff --git a/test/classes.spec.js b/test/classes.spec.js index 546431d..617d928 100644 --- a/test/classes.spec.js +++ b/test/classes.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); class Person { @@ -34,15 +34,15 @@ class Person { } } -const rule1 = { +const rule1 = new Rule({ name: 'mood is great if 200 stars or more', when: facts => facts.user.getStars() >= 200, then: (facts) => { facts.user.setMood('great'); }, -}; +}); -const rule2 = { +const rule2 = new Rule({ name: 'mark applicable if mood is great and salery greater 1000', when: [ facts => facts.user.getMood() === 'great', @@ -51,7 +51,7 @@ const rule2 = { then: (facts) => { facts.result = true; }, -}; +}); describe('Rules.evaluate() / classes with getters and setters', () => { it('should set mood in 1 pass', async () => { diff --git a/test/errors.spec.js b/test/errors.spec.js index 220b892..bebe9ff 100644 --- a/test/errors.spec.js +++ b/test/errors.spec.js @@ -1,5 +1,5 @@ const assert = require('assert'); -const Rools = require('..'); +const { Rools, Rule } = require('..'); const { frank } = require('./facts/users')(); const { good } = require('./facts/weather')(); const { @@ -7,13 +7,13 @@ const { } = require('./rules/mood'); require('./setup'); -describe('Rules.evaluate()', () => { +describe('Rules.evaluate() / errors', () => { it('should not fail if `when` throws error', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #1', when: facts => facts.bla.blub === 'blub', // TypeError: Cannot read property 'blub' of undefined then: () => {}, - }; + }); const rools = new Rools({ logging: { error: false } }); const facts = { user: frank, weather: good }; await rools.register([brokenRule, ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome]); @@ -27,13 +27,13 @@ describe('Rules.evaluate()', () => { }); it('should fail if `then` throws error', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); const rools = new Rools({ logging: { error: false } }); const facts = { user: frank, weather: good }; await rools.register([brokenRule, ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome]); diff --git a/test/extend.spec.js b/test/extend.spec.js new file mode 100644 index 0000000..dc37029 --- /dev/null +++ b/test/extend.spec.js @@ -0,0 +1,98 @@ +const { Rools, Rule } = require('..'); +require('./setup'); + +describe('Rules.evaluate() / extend', () => { + const sequence = []; + const rule1 = new Rule({ + name: 'rule1', + when: facts => facts.fact1, + then: () => { sequence.push(1); }, + }); + const rule2 = new Rule({ + name: 'rule2', + extend: rule1, + when: facts => facts.fact2, + then: () => { sequence.push(2); }, + }); + const rule3 = new Rule({ + name: 'rule3', + extend: rule2, + when: facts => facts.fact3, + then: () => { sequence.push(3); }, + }); + const rule4 = new Rule({ + name: 'rule4', + extend: rule2, + when: facts => facts.fact4, + then: () => { sequence.push(4); }, + }); + const rule5 = new Rule({ + name: 'rule5', + extend: [rule3, rule4], + when: facts => facts.fact5, + then: () => { sequence.push(5); }, + }); + const facts = { + fact1: true, + fact2: true, + fact3: true, + fact4: true, + fact5: true, + }; + + it('should fire rule with higher specificity first / 1 extended rule', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, rule2]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([2, 1]); + }); + + it('should fire rule with higher specificity first / 2 extended rules', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, rule2, rule3]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([3, 2, 1]); + }); + + it('should fire rule with higher specificity, then order of registration / 3 extended rules', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, rule2, rule3, rule4]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([3, 4, 2, 1]); + }); + + it('should fire rule with highest prio, then higher specificity / 2 extended rules / 1', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([1, 3, 2]); + }); + + it('should fire rule with highest prio, then higher specificity / 2 extended rules / 2', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, new Rule({ ...rule2, priority: 10 }), rule3]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([2, 3, 1]); + }); + + it('should fire rule with highest prio, then higher specificity / 3 extended rules', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, rule2, rule3, new Rule({ ...rule4, priority: 10 })]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([4, 3, 2, 1]); + }); + + it('should fire rule with highest specificity, then order of registration / 4 extended rules', async () => { + sequence.length = 0; // reset + const rools = new Rools(); + await rools.register([rule1, rule2, rule3, rule4, rule5]); + await rools.evaluate(facts); + expect(sequence).to.be.deep.equal([5, 3, 4, 2, 1]); + }); +}); diff --git a/test/final.spec.js b/test/final.spec.js index 6d7506b..7072ac9 100644 --- a/test/final.spec.js +++ b/test/final.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); const { frank } = require('./facts/users')(); const { good } = require('./facts/weather')(); const { @@ -14,8 +14,8 @@ describe('Rules.evaluate() / final', () => { await rools.register([ ruleGoWalking, ruleStayAtHome, - { ...ruleMoodGreat, final: true }, - { ...ruleMoodSad, final: true }, + new Rule({ ...ruleMoodGreat, final: true }), + new Rule({ ...ruleMoodSad, final: true }), ]); }); diff --git a/test/logging.spec.js b/test/logging.spec.js index 1b79bab..e737419 100644 --- a/test/logging.spec.js +++ b/test/logging.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); const { frank } = require('./facts/users')(); const { good } = require('./facts/weather')(); const { @@ -19,13 +19,13 @@ describe('Rules.evaluate() / delegate logging', () => { }); it('should log errors', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); let counter = 0; const spy = () => { counter += 1; @@ -41,13 +41,13 @@ describe('Rules.evaluate() / delegate logging', () => { }); it('should log errors by default', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); let counter = 0; const spy = () => { counter += 1; @@ -82,13 +82,13 @@ describe('Rules.evaluate() / console logging', () => { }); it('should log errors', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); const rools = new Rools({ logging: { error: true, debug: false } }); await rools.register([brokenRule, ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome]); try { @@ -100,13 +100,13 @@ describe('Rules.evaluate() / console logging', () => { }); it('should log errors by default', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); const rools = new Rools(); await rools.register([brokenRule, ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome]); try { @@ -118,13 +118,13 @@ describe('Rules.evaluate() / console logging', () => { }); it('should log errors by default / 2', async () => { - const brokenRule = { + const brokenRule = new Rule({ name: 'broken rule #2', when: () => true, // fire immediately then: (facts) => { facts.bla.blub = 'blub'; // TypeError: Cannot read property 'blub' of undefined }, - }; + }); const rools = new Rools({}); await rools.register([brokenRule, ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome]); try { diff --git a/test/longer.spec.js b/test/longer.spec.js index 3dcd736..58deb0f 100644 --- a/test/longer.spec.js +++ b/test/longer.spec.js @@ -1,80 +1,80 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / longer cycle', () => { let rools; before(async () => { - const rule0 = { + const rule0 = new Rule({ name: 'rule0', when: facts => facts.user.stars === 0, then: (facts) => { facts.user.stars += 1; }, - }; - const rule1 = { + }); + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.stars === 1, then: (facts) => { facts.user.stars += 1; }, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.stars === 2, then: (facts) => { facts.user.stars += 1; }, - }; - const rule3 = { + }); + const rule3 = new Rule({ name: 'rule3', when: facts => facts.user.stars === 3, then: (facts) => { facts.user.stars += 1; }, - }; - const rule4 = { + }); + const rule4 = new Rule({ name: 'rule4', when: facts => facts.user.stars === 4, then: (facts) => { facts.user.stars += 1; }, - }; - const rule5 = { + }); + const rule5 = new Rule({ name: 'rule5', when: facts => facts.user.stars === 5, then: (facts) => { facts.user.stars += 1; }, - }; - const rule6 = { + }); + const rule6 = new Rule({ name: 'rule6', when: facts => facts.user.stars === 6, then: (facts) => { facts.user.stars += 1; }, - }; - const rule7 = { + }); + const rule7 = new Rule({ name: 'rule7', when: facts => facts.user.stars === 7, then: (facts) => { facts.user.stars += 1; }, - }; - const rule8 = { + }); + const rule8 = new Rule({ name: 'rule8', when: facts => facts.user.stars === 8, then: (facts) => { facts.user.stars += 1; }, - }; - const rule9 = { + }); + const rule9 = new Rule({ name: 'rule9', when: facts => facts.user.stars === 9, then: (facts) => { facts.user.stars += 1; }, - }; + }); rools = new Rools(); await rools.register([rule7, rule0, rule2, rule3, rule1, rule6, rule8, rule4, rule9, rule5]); }); diff --git a/test/premises.spec.js b/test/premises.spec.js index 9c2c7c8..15343b2 100644 --- a/test/premises.spec.js +++ b/test/premises.spec.js @@ -1,18 +1,18 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.register() / optimization of premises', () => { it('should not merge premises if not identical', async () => { - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.name === 'frank', then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.name === 'michael', then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(2); @@ -20,16 +20,16 @@ describe('Rules.register() / optimization of premises', () => { it('should merge premises if identical / reference / arrow function', async () => { const isFrank = facts => facts.user.name === 'frank'; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: isFrank, then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: isFrank, then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); @@ -39,68 +39,68 @@ describe('Rules.register() / optimization of premises', () => { function isFrank(facts) { return facts.user.name === 'frank'; } - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: isFrank, then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: isFrank, then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); }); it('should merge premises if identical / hash / arrow function', async () => { - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.name === 'frank', then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.name === 'frank', then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); }); it('should merge premises if identical / hash / classic function()', async () => { - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: function p(facts) { return facts.user.name === 'frank'; }, then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: function p(facts) { return facts.user.name === 'frank'; }, then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); }); it('should merge premises if identical / hash / slightly different', async () => { - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.name === 'frank', then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.name === "frank", // eslint-disable-line quotes then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); @@ -109,16 +109,16 @@ describe('Rules.register() / optimization of premises', () => { it('should not merge premises if not identical / with Date object', async () => { const date1 = new Date('2000-01-01'); const date2 = new Date('1990-01-01'); - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.birthdate > date1, then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.birthdate > date2, then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(2); @@ -126,16 +126,16 @@ describe('Rules.register() / optimization of premises', () => { it('should merge premises if identical / with Date object', async () => { const date = new Date('2000-01-01'); - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.user.birthdate > date, then: () => {}, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: facts => facts.user.birthdate > date, then: () => {}, - }; + }); const rools = new Rools(); await rools.register([rule1, rule2]); expect(rools.rules.premises.length).to.be.equal(1); diff --git a/test/priority.spec.js b/test/priority.spec.js index 48f5459..c14bcba 100644 --- a/test/priority.spec.js +++ b/test/priority.spec.js @@ -1,26 +1,26 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / priority', () => { const sequence = []; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.fact1, then: () => { sequence.push(1); }, - }; + }); - const rule2 = { + const rule2 = new Rule({ name: 'rule2', when: facts => facts.fact1, then: () => { sequence.push(2); }, - }; + }); - const rule3 = { + const rule3 = new Rule({ name: 'rule3', when: facts => facts.fact1, then: () => { sequence.push(3); }, - }; + }); const facts = { fact1: true, @@ -29,7 +29,11 @@ describe('Rules.evaluate() / priority', () => { it('should fire priority 10 first, then in order of registration', async () => { sequence.length = 0; const rools = new Rools(); - await rools.register([rule1, { ...rule2, priority: 10 }, rule3]); + await rools.register([ + rule1, + new Rule({ ...rule2, priority: 10 }), + rule3, + ]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([2, 1, 3]); }); @@ -37,7 +41,11 @@ describe('Rules.evaluate() / priority', () => { it('should fire in order of registration, finally negative priority -10', async () => { sequence.length = 0; const rools = new Rools(); - await rools.register([{ ...rule1, priority: -10 }, rule2, rule3]); + await rools.register([ + new Rule({ ...rule1, priority: -10 }), + rule2, + rule3, + ]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([2, 3, 1]); }); @@ -45,7 +53,11 @@ describe('Rules.evaluate() / priority', () => { it('should fire in order of priority 10, 0, -10', async () => { sequence.length = 0; const rools = new Rools(); - await rools.register([{ ...rule1, priority: -10 }, rule2, { ...rule3, priority: 10 }]); + await rools.register([ + new Rule({ ...rule1, priority: -10 }), + rule2, + new Rule({ ...rule3, priority: 10 }), + ]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([3, 2, 1]); }); @@ -54,9 +66,9 @@ describe('Rules.evaluate() / priority', () => { sequence.length = 0; const rools = new Rools(); await rools.register([ - { ...rule1, priority: 10 }, - { ...rule2, priority: 10 }, - { ...rule3, priority: 10 }, + new Rule({ ...rule1, priority: 10 }), + new Rule({ ...rule2, priority: 10 }), + new Rule({ ...rule3, priority: 10 }), ]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([1, 2, 3]); diff --git a/test/reevaluate.spec.js b/test/reevaluate.spec.js index 0796ec2..bb13c94 100644 --- a/test/reevaluate.spec.js +++ b/test/reevaluate.spec.js @@ -1,25 +1,25 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / re-evaluate', () => { it('should re-evaluate premises only if facts are changed / row', async () => { const premisesEvaluated = []; const actionsFired = []; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: (facts) => { premisesEvaluated.push(1); return facts.fact1; }, then: (facts) => { actionsFired.push(1); facts.fact2 = true; }, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: (facts) => { premisesEvaluated.push(2); return facts.fact2; }, then: (facts) => { actionsFired.push(2); facts.fact3 = true; }, - }; - const rule3 = { + }); + const rule3 = new Rule({ name: 'rule3', when: (facts) => { premisesEvaluated.push(3); return facts.fact3; }, then: () => { actionsFired.push(3); }, - }; + }); const facts = { fact1: true, fact2: false, @@ -35,21 +35,21 @@ describe('Rules.evaluate() / re-evaluate', () => { it('should re-evaluate premises only if facts are changed / complex', async () => { const premisesEvaluated = []; const actionsFired = []; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: (facts) => { premisesEvaluated.push(1); return facts.fact1; }, then: (facts) => { actionsFired.push(1); facts.fact2 = true; }, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: (facts) => { premisesEvaluated.push(2); return facts.fact2; }, then: (facts) => { actionsFired.push(2); facts.fact1 = false; facts.fact3 = true; }, - }; - const rule3 = { + }); + const rule3 = new Rule({ name: 'rule3', when: (facts) => { premisesEvaluated.push(3); return facts.fact3; }, then: (facts) => { actionsFired.push(3); facts.fact2 = false; }, - }; + }); const facts = { fact1: true, fact2: false, diff --git a/test/refraction.spec.js b/test/refraction.spec.js index 053dc3a..86a5505 100644 --- a/test/refraction.spec.js +++ b/test/refraction.spec.js @@ -1,21 +1,21 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / refraction', () => { const spy = sinon.spy(); - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.fact1, then: (facts) => { facts.fact1 = false; facts.fact1 = true; spy(); }, priority: 10, - }; + }); - const rule2 = { + const rule2 = new Rule({ name: 'rule2', when: facts => facts.fact2, then: (facts) => { facts.fact1 = false; facts.fact1 = true; }, - }; + }); const facts = { fact1: true, diff --git a/test/register.spec.js b/test/register.spec.js deleted file mode 100644 index 2687c4f..0000000 --- a/test/register.spec.js +++ /dev/null @@ -1,74 +0,0 @@ -const assert = require('assert'); -const { ruleMoodGreat: rule } = require('./rules/mood'); -const Rools = require('..'); -require('./setup'); - -describe('Rules.register()', () => { - let rools; - - before(() => { - rools = new Rools(); - }); - - it('should not fail if rules are correct', async () => { - try { - await rools.register([{ ...rule }]); - } catch (error) { - assert.fail(error); - } - }); - - it('should fail if rule has no "name"', async () => { - try { - await rools.register([{ ...rule, name: undefined }]); - assert.fail(); - } catch (error) { - // correct! - } - }); - - it('should fail if rule has no "when"', async () => { - try { - await rools.register([{ ...rule, when: undefined }]); - assert.fail(); - } catch (error) { - // correct! - } - }); - - it('should fail if rule has no "then"', async () => { - try { - await rools.register([{ ...rule, then: undefined }]); - assert.fail(); - } catch (error) { - // correct! - } - }); - - it('should fail if rule "when" is neither function nor array', async () => { - try { - await rools.register([{ ...rule, when: 'not a function' }]); - assert.fail(); - } catch (error) { - // correct! - } - }); - - it('should fail if rule "when" is an array with a non-function element', async () => { - try { - await rools.register([{ ...rule, when: ['not a function'] }]); - assert.fail(); - } catch (error) { - // correct! - } - }); - - it('should fail if rule "then" is not a function', async () => { - try { - await rools.register([{ ...rule, then: 'not a function' }]); - assert.fail(); - } catch (error) { - // correct! - } - }); -}); diff --git a/test/result.spec.js b/test/result.spec.js index 077a0a3..e068b26 100644 --- a/test/result.spec.js +++ b/test/result.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools } = require('..'); const { frank } = require('./facts/users')(); const { good } = require('./facts/weather')(); const { diff --git a/test/rules/availability.js b/test/rules/availability.js index 9b5612e..3e434f6 100644 --- a/test/rules/availability.js +++ b/test/rules/availability.js @@ -1,3 +1,5 @@ +const { Rule } = require('../..'); + const availabilityCheck = (address) => { // eslint-disable-line arrow-body-style return new Promise((resolve) => { setTimeout(() => { @@ -11,20 +13,20 @@ const availabilityCheck = (address) => { // eslint-disable-line arrow-body-style }); }; -const rule1 = { +const rule1 = new Rule({ name: 'check availability of products (async await)', when: facts => facts.user.address.country === 'germany', then: async (facts) => { facts.products = await availabilityCheck(facts.user.address); }, -}; +}); -const rule2 = { +const rule2 = new Rule({ name: 'check availability of products (promises)', when: facts => facts.user.address.country === 'germany', then: facts => availabilityCheck(facts.user.address) .then((result) => { facts.products = result; }), -}; +}); module.exports = { rule1, rule2 }; diff --git a/test/rules/mood.js b/test/rules/mood.js index 592598d..8669de1 100644 --- a/test/rules/mood.js +++ b/test/rules/mood.js @@ -1,20 +1,22 @@ -const ruleMoodGreat = { +const { Rule } = require('../..'); + +const ruleMoodGreat = new Rule({ name: 'mood is great if 200 stars or more', when: facts => facts.user.stars >= 200, then: (facts) => { facts.user.mood = 'great'; }, -}; +}); -const ruleMoodSad = { +const ruleMoodSad = new Rule({ name: 'mood is sad if less than 200 stars', when: facts => facts.user.stars < 200, then: (facts) => { facts.user.mood = 'sad'; }, -}; +}); -const ruleGoWalking = { +const ruleGoWalking = new Rule({ name: 'go for a walk if mood is great and the weather is fine', when: [ facts => facts.user.mood === 'great', @@ -24,9 +26,9 @@ const ruleGoWalking = { then: (facts) => { facts.goWalking = true; }, -}; +}); -const ruleStayAtHome = { +const ruleStayAtHome = new Rule({ name: 'stay at home if mood is sad or the weather is bad', when: [ facts => facts.weather.rainy || facts.user.mood === 'sad', @@ -34,7 +36,7 @@ const ruleStayAtHome = { then: (facts) => { facts.stayAtHome = true; }, -}; +}); module.exports = { ruleMoodGreat, ruleMoodSad, ruleGoWalking, ruleStayAtHome, diff --git a/test/simple.spec.js b/test/simple.spec.js index 0297712..72dc271 100644 --- a/test/simple.spec.js +++ b/test/simple.spec.js @@ -1,4 +1,4 @@ -const Rools = require('..'); +const { Rools } = require('..'); const { frank, michael } = require('./facts/users')(); const { good, bad } = require('./facts/weather')(); const { diff --git a/test/specificity.spec.js b/test/specificity.spec.js index a72decf..5c03c5e 100644 --- a/test/specificity.spec.js +++ b/test/specificity.spec.js @@ -1,22 +1,22 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / specificity', () => { const sequence = []; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.fact1, then: () => { sequence.push(1); }, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: [ facts => facts.fact1, facts => facts.fact2, ], then: () => { sequence.push(2); }, - }; - const rule3 = { + }); + const rule3 = new Rule({ name: 'rule3', when: [ facts => facts.fact1, @@ -24,8 +24,8 @@ describe('Rules.evaluate() / specificity', () => { facts => facts.fact3, ], then: () => { sequence.push(3); }, - }; - const rule4 = { + }); + const rule4 = new Rule({ name: 'rule4', when: [ facts => facts.fact1, @@ -33,7 +33,7 @@ describe('Rules.evaluate() / specificity', () => { facts => facts.fact4, ], then: () => { sequence.push(4); }, - }; + }); const facts = { fact1: true, fact2: true, @@ -68,7 +68,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([{ ...rule1, priority: 10 }, rule2, rule3]); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([1, 3, 2]); }); @@ -76,7 +76,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity / 2', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([rule1, { ...rule2, priority: 10 }, rule3]); + await rools.register([rule1, new Rule({ ...rule2, priority: 10 }), rule3]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([2, 3, 1]); }); @@ -84,7 +84,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity / 3', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([rule1, rule2, rule3, { ...rule4, priority: 10 }]); + await rools.register([rule1, rule2, rule3, new Rule({ ...rule4, priority: 10 })]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([4, 3, 2, 1]); }); diff --git a/test/strategy.spec.js b/test/strategy.spec.js index 56a3365..81908a5 100644 --- a/test/strategy.spec.js +++ b/test/strategy.spec.js @@ -1,22 +1,22 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); -describe('Rules.evaluate() / specificity', () => { +describe('Rules.evaluate() / strategy', () => { const sequence = []; - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.fact1, then: () => { sequence.push(1); }, - }; - const rule2 = { + }); + const rule2 = new Rule({ name: 'rule2', when: [ facts => facts.fact1, facts => facts.fact2, ], then: () => { sequence.push(2); }, - }; - const rule3 = { + }); + const rule3 = new Rule({ name: 'rule3', when: [ facts => facts.fact1, @@ -24,8 +24,8 @@ describe('Rules.evaluate() / specificity', () => { facts => facts.fact3, ], then: () => { sequence.push(3); }, - }; - const rule4 = { + }); + const rule4 = new Rule({ name: 'rule4', when: [ facts => facts.fact1, @@ -33,7 +33,7 @@ describe('Rules.evaluate() / specificity', () => { facts => facts.fact4, ], then: () => { sequence.push(4); }, - }; + }); const facts = { fact1: true, fact2: true, @@ -55,7 +55,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity / strategy "ps" / 1', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([{ ...rule1, priority: 10 }, rule2, rule3]); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); await rools.evaluate(facts); expect(sequence).to.be.deep.equal([1, 3, 2]); }); @@ -63,7 +63,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity / strategy "ps" / 2', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([{ ...rule1, priority: 10 }, rule2, rule3]); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); await rools.evaluate(facts, { strategy: 'ps' }); expect(sequence).to.be.deep.equal([1, 3, 2]); }); @@ -71,7 +71,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with highest prio, then higher specificity / strategy "ps" / 3', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([{ ...rule1, priority: 10 }, rule2, rule3]); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); await rools.evaluate(facts, {}); expect(sequence).to.be.deep.equal([1, 3, 2]); }); @@ -79,7 +79,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with higher specificity, then highest prio / strategy "sp" / 1', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([{ ...rule1, priority: 10 }, rule2, rule3]); + await rools.register([new Rule({ ...rule1, priority: 10 }), rule2, rule3]); await rools.evaluate(facts, { strategy: 'sp' }); expect(sequence).to.be.deep.equal([3, 2, 1]); }); @@ -87,7 +87,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with higher specificity, then highest prio / strategy "sp" / 2', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([rule1, { ...rule2, priority: 10 }, rule3]); + await rools.register([rule1, new Rule({ ...rule2, priority: 10 }), rule3]); await rools.evaluate(facts, { strategy: 'sp' }); expect(sequence).to.be.deep.equal([3, 2, 1]); }); @@ -103,7 +103,7 @@ describe('Rules.evaluate() / specificity', () => { it('should fire rule with higher specificity, then highest prio / strategy "sp" / 4', async () => { sequence.length = 0; // reset const rools = new Rools(); - await rools.register([rule1, rule2, rule3, { ...rule4, priority: 10 }]); + await rools.register([rule1, rule2, rule3, new Rule({ ...rule4, priority: 10 })]); await rools.evaluate(facts, { strategy: 'sp' }); expect(sequence).to.be.deep.equal([4, 3, 2, 1]); }); diff --git a/test/withdraw.spec.js b/test/withdraw.spec.js index efcfaee..e598263 100644 --- a/test/withdraw.spec.js +++ b/test/withdraw.spec.js @@ -1,20 +1,20 @@ -const Rools = require('..'); +const { Rools, Rule } = require('..'); require('./setup'); describe('Rules.evaluate() / withdraw', () => { const spy = sinon.spy(); - const rule1 = { + const rule1 = new Rule({ name: 'rule1', when: facts => facts.fact1, then: (facts) => { facts.fact2 = false; }, - }; + }); - const rule2 = { + const rule2 = new Rule({ name: 'rule2', when: facts => facts.fact2, then: () => { spy(); }, - }; + }); const facts = { fact1: true,