From b8b6f30946413d567ebb8d70a5f54344ce2847d5 Mon Sep 17 00:00:00 2001 From: Emile Fokkema <146825801+emilefokkemanavara@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:02:30 +0000 Subject: [PATCH] re-apply the changes from !390 onto v8 --- test/engine-event.test.mjs | 64 ++++++++++++++++++++++++++++++++++++++ test/types.test-d.mts | 19 ++++++++++- types/index.d.ts | 41 +++++++++++++++++++++++- 3 files changed, 122 insertions(+), 2 deletions(-) diff --git a/test/engine-event.test.mjs b/test/engine-event.test.mjs index 11bae3e..7cdabbc 100644 --- a/test/engine-event.test.mjs +++ b/test/engine-event.test.mjs @@ -13,6 +13,10 @@ describe("Engine: event", () => { canOrderDrinks: true, }, }; + + const awesomeEvent = { + type: 'awesome' + } /** * sets up a simple 'any' rule with 2 conditions */ @@ -85,6 +89,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 = ruleFactory(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 = ruleFactory(ruleOptions) + engine.addRule(rule) + engine.addFact('age', 21) + } + describe("engine events: simple", () => { beforeEach(() => simpleSetup()); @@ -647,4 +691,24 @@ describe("Engine: event", () => { expect(JSON.stringify(ruleResult)).toBe(expected); }); }); + + describe('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)).toEqual(expected) + }) + }) + + describe('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).toEqual(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)).toEqual(expected) + }) + }) }); diff --git a/test/types.test-d.mts b/test/types.test-d.mts index 86c3f5a..69bb309 100644 --- a/test/types.test-d.mts +++ b/test/types.test-d.mts @@ -15,6 +15,10 @@ import rulesEngine, { RuleProperties, RuleResult, RuleSerializable, + TopLevelConditionResult, + AnyConditionsResult, + AllConditionsResult, + NotConditionsResult } from "../types/index.js"; // setup basic fixture data @@ -63,7 +67,20 @@ describe("type tests", () => { const engine = rulesEngine([complexRuleProps]); it("engine run returns a promise of the result", () => { - expectTypeOf>(engine.run({ displayMessage: true })); + const result = engine.run({ displayMessage: true }) + expectTypeOf>(result); + + const topLevelConditionResult = result.then(r => r.results[0].conditions); + expectTypeOf>(topLevelConditionResult) + + const topLevelAnyConditionsResult = topLevelConditionResult.then(r => (r as AnyConditionsResult).result); + expectTypeOf>(topLevelAnyConditionsResult) + + const topLevelAllConditionsResult = topLevelConditionResult.then(r => (r as AllConditionsResult).result); + expectTypeOf>(topLevelAllConditionsResult) + + const topLevelNotConditionsResult = topLevelConditionResult.then(r => (r as NotConditionsResult).result); + expectTypeOf>(topLevelNotConditionsResult) }); describe("rule tests", () => { diff --git a/types/index.d.ts b/types/index.d.ts index bf3fd2c..596cedc 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -170,12 +170,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 { @@ -193,6 +203,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; @@ -203,25 +221,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