diff --git a/test/engine-event.test.js b/test/engine-event.test.js index c929d92..d86f75c 100644 --- a/test/engine-event.test.js +++ b/test/engine-event.test.js @@ -20,6 +20,10 @@ describe('Engine: event', () => { canOrderDrinks: true } } + + const awesomeEvent = { + type: 'awesome' + } /** * sets up a simple 'any' rule with 2 conditions */ @@ -92,6 +96,46 @@ describe('Engine: event', () => { engine.addFact('gender', 'male') // gender succeeds } + function setupWithConditionReference () { + const conditionName = 'awesomeCondition' + const conditions = { + any: [{ condition: conditionName }] + } + engine = engineFactory() + const ruleOptions = { conditions, event: awesomeEvent, priority: 100 } + const rule = factories.rule(ruleOptions) + engine.addRule(rule) + engine.setCondition(conditionName, { + all: [{ + name: 'over 21', + fact: 'age', + operator: 'greaterThanInclusive', + value: 21 + }] + }) + engine.addFact('age', 21) + } + + function setupWithUndefinedCondition () { + const conditionName = 'conditionThatIsNotDefined' + const conditions = { + any: [ + { condition: conditionName, name: 'nameOfTheUndefinedConditionReference' }, + { + name: 'over 21', + fact: 'age', + operator: 'greaterThanInclusive', + value: 21 + } + ] + } + engine = engineFactory([], { allowUndefinedConditions: true }) + const ruleOptions = { conditions, event: awesomeEvent, priority: 100 } + const rule = factories.rule(ruleOptions) + engine.addRule(rule) + engine.addFact('age', 21) + } + context('engine events: simple', () => { beforeEach(() => simpleSetup()) @@ -602,4 +646,24 @@ describe('Engine: event', () => { expect(JSON.stringify(ruleResult)).to.equal(expected) }) }) + + context('rule events: json serializing with condition reference', () => { + beforeEach(() => setupWithConditionReference()) + it('serializes properties', async () => { + const { results: [ruleResult] } = await engine.run() + const expected = '{"conditions":{"priority":1,"any":[{"priority":1,"all":[{"name":"over 21","operator":"greaterThanInclusive","value":21,"fact":"age","factResult":21,"result":true}]}]},"event":{"type":"awesome"},"priority":100,"result":true}' + expect(JSON.stringify(ruleResult)).to.equal(expected) + }) + }) + + context('rule events: json serializing with condition reference that is undefined', () => { + beforeEach(() => setupWithUndefinedCondition()) + it('serializes properties', async () => { + const { results: [ruleResult] } = await engine.run() + const { conditions: { any: [conditionReference] } } = ruleResult + expect(conditionReference.result).to.equal(false) + const expected = '{"conditions":{"priority":1,"any":[{"name":"nameOfTheUndefinedConditionReference","condition":"conditionThatIsNotDefined"},{"name":"over 21","operator":"greaterThanInclusive","value":21,"fact":"age","factResult":21,"result":true}]},"event":{"type":"awesome"},"priority":100,"result":true}' + expect(JSON.stringify(ruleResult)).to.equal(expected) + }) + }) }) diff --git a/types/index.d.ts b/types/index.d.ts index 81f08dc..827e79f 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -153,12 +153,22 @@ export type RuleSerializable = Pick< "conditions" | "event" | "name" | "priority" >; +export type RuleResultSerializable = Pick< + Required, + "name" | "event" | "priority" | "result"> & { + conditions: TopLevelConditionResultSerializable + } + export interface RuleResult { name: string; - conditions: TopLevelCondition; + conditions: TopLevelConditionResult; event?: Event; priority?: number; result: any; + toJSON(): string; + toJSON( + stringify: T + ): T extends true ? string : RuleResultSerializable; } export class Rule implements RuleProperties { @@ -176,6 +186,14 @@ export class Rule implements RuleProperties { ): T extends true ? string : RuleSerializable; } +interface BooleanConditionResultProperties { + result?: boolean +} + +interface ConditionResultProperties extends BooleanConditionResultProperties { + factResult?: unknown +} + interface ConditionProperties { fact: string; operator: string; @@ -186,25 +204,46 @@ interface ConditionProperties { name?: string; } +type ConditionPropertiesResult = ConditionProperties & ConditionResultProperties + type NestedCondition = ConditionProperties | TopLevelCondition; +type NestedConditionResult = ConditionPropertiesResult | TopLevelConditionResult; type AllConditions = { all: NestedCondition[]; name?: string; priority?: number; }; +type AllConditionsResult = AllConditions & { + all: NestedConditionResult[] +} & BooleanConditionResultProperties type AnyConditions = { any: NestedCondition[]; name?: string; priority?: number; }; +type AnyConditionsResult = AnyConditions & { + any: NestedConditionResult[] +} & BooleanConditionResultProperties type NotConditions = { not: NestedCondition; name?: string; priority?: number }; +type NotConditionsResult = NotConditions & {not: NestedConditionResult} & BooleanConditionResultProperties; type ConditionReference = { condition: string; name?: string; priority?: number; }; +type ConditionReferenceResult = ConditionReference & BooleanConditionResultProperties export type TopLevelCondition = | AllConditions | AnyConditions | NotConditions | ConditionReference; +export type TopLevelConditionResult = + | AllConditionsResult + | AnyConditionsResult + | NotConditionsResult + | ConditionReferenceResult +export type TopLevelConditionResultSerializable = + | AllConditionsResult + | AnyConditionsResult + | NotConditionsResult + | ConditionReference diff --git a/types/index.test-d.ts b/types/index.test-d.ts index 6d5b37b..a7e1120 100644 --- a/types/index.test-d.ts +++ b/types/index.test-d.ts @@ -14,7 +14,11 @@ import rulesEngine, { Rule, RuleProperties, RuleResult, - RuleSerializable + RuleSerializable, + TopLevelConditionResult, + AnyConditionsResult, + AllConditionsResult, + NotConditionsResult } from "../"; // setup basic fixture data @@ -126,7 +130,20 @@ engine.on<{ foo: Array }>('foo', (event, almanac, ruleResult) => { }) // Run the Engine -expectType>(engine.run({ displayMessage: true })); +const result = engine.run({ displayMessage: true }) +expectType>(result); + +const topLevelConditionResult = result.then(r => r.results[0].conditions); +expectType>(topLevelConditionResult) + +const topLevelAnyConditionsResult = topLevelConditionResult.then(r => (r as AnyConditionsResult).result); +expectType>(topLevelAnyConditionsResult) + +const topLevelAllConditionsResult = topLevelConditionResult.then(r => (r as AllConditionsResult).result); +expectType>(topLevelAllConditionsResult) + +const topLevelNotConditionsResult = topLevelConditionResult.then(r => (r as NotConditionsResult).result); +expectType>(topLevelNotConditionsResult) // Alamanac tests const almanac: Almanac = (await engine.run()).almanac;