@frontegg/entitlements-javascript-commons 1.0.0-alpha.1 → 1.0.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yaml +1 -1
- package/.releaserc.yaml +2 -2
- package/docs/CHANGELOG.md +76 -0
- package/jest.config.js +26 -0
- package/package.json +2 -2
- package/src/conditions/condition.evaluator.ts +20 -0
- package/src/conditions/index.ts +2 -0
- package/src/conditions/tests/condition.evaluator.spec.ts +52 -0
- package/src/conditions/types.ts +8 -0
- package/src/operations/boolean/index.ts +9 -0
- package/src/operations/boolean/operations.spec.ts +13 -0
- package/src/operations/boolean/operations.ts +6 -0
- package/src/operations/boolean/types.ts +3 -0
- package/src/operations/components/operation.resolver.ts +18 -0
- package/src/operations/components/tests/operation.resolver.spec.ts +8 -0
- package/src/operations/date/index.ts +18 -0
- package/src/operations/date/operations.spec.ts +45 -0
- package/src/operations/date/operations.ts +20 -0
- package/src/operations/date/types.ts +10 -0
- package/src/operations/numeric/index.ts +22 -0
- package/src/operations/numeric/operations.spec.ts +63 -0
- package/src/operations/numeric/operations.ts +26 -0
- package/src/operations/numeric/tests/operations.spec.ts +64 -0
- package/src/operations/numeric/types.ts +10 -0
- package/src/operations/string/index.ts +20 -0
- package/src/operations/string/operations.spec.ts +38 -0
- package/src/operations/string/operations.ts +32 -0
- package/src/operations/string/tests/operations.spec.ts +38 -0
- package/src/operations/string/types.ts +9 -0
- package/src/operations/types/index.ts +22 -0
- package/src/operations/types/operation.enum.ts +25 -0
- package/src/rules/index.ts +2 -0
- package/src/rules/rule.evaluator.ts +14 -0
- package/src/rules/tests/rule.evaluator.spec.ts +140 -0
- package/src/rules/types.ts +28 -0
- package/src/index.spec.ts +0 -5
package/.releaserc.yaml
CHANGED
package/docs/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,79 @@
|
|
|
1
|
+
# [1.0.0-alpha.2](https://github.com/frontegg/entitlements-javascript-commons/compare/v-1.0.0-alpha.1...v-1.0.0-alpha.2) (2023-10-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **rule:** add rule evaluator ([#6](https://github.com/frontegg/entitlements-javascript-commons/issues/6)) ([5fbf4da](https://github.com/frontegg/entitlements-javascript-commons/commit/5fbf4da00a3d9df2908d8899723a64b1bd80a7c2))
|
|
7
|
+
|
|
8
|
+
# 1.0.0-alpha.1 (2023-10-03)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* create pipeline and release infra ([16fd6d1](https://github.com/frontegg/entitlements-javascript-commons/commit/16fd6d165cff4c3ae28e2392db2480d41dd591b1))
|
|
14
|
+
* **conditions:** add condition evaluator ([#5](https://github.com/frontegg/entitlements-javascript-commons/issues/5)) ([5ca2446](https://github.com/frontegg/entitlements-javascript-commons/commit/5ca24465a76b9fa103977e5600a6d870da5520cb))
|
|
15
|
+
* **operations:** add numeric operations ([#3](https://github.com/frontegg/entitlements-javascript-commons/issues/3)) ([#10](https://github.com/frontegg/entitlements-javascript-commons/issues/10)) ([0b3de2f](https://github.com/frontegg/entitlements-javascript-commons/commit/0b3de2f7f1aede036ec63e4fadf898dcf5ad32a4))
|
|
16
|
+
* **operations:** add string operations ([#2](https://github.com/frontegg/entitlements-javascript-commons/issues/2)) ([e2e63a7](https://github.com/frontegg/entitlements-javascript-commons/commit/e2e63a74211a723dc326918e42e2093fcca86779))
|
|
17
|
+
|
|
18
|
+
# 1.0.0-alpha.1 (2023-10-03)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
### Bug Fixes
|
|
22
|
+
|
|
23
|
+
* fix changelog ([7c7515b](https://github.com/frontegg/entitlements-javascript-commons/commit/7c7515b78e980695506e061dc8f942669b97626f))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
### Features
|
|
27
|
+
|
|
28
|
+
* create pipeline and release infra ([16fd6d1](https://github.com/frontegg/entitlements-javascript-commons/commit/16fd6d165cff4c3ae28e2392db2480d41dd591b1))
|
|
29
|
+
* **conditions:** add condition evaluator ([#5](https://github.com/frontegg/entitlements-javascript-commons/issues/5)) ([5ca2446](https://github.com/frontegg/entitlements-javascript-commons/commit/5ca24465a76b9fa103977e5600a6d870da5520cb))
|
|
30
|
+
* **operations:** add numeric operations ([#3](https://github.com/frontegg/entitlements-javascript-commons/issues/3)) ([#10](https://github.com/frontegg/entitlements-javascript-commons/issues/10)) ([0b3de2f](https://github.com/frontegg/entitlements-javascript-commons/commit/0b3de2f7f1aede036ec63e4fadf898dcf5ad32a4))
|
|
31
|
+
* **operations:** add string operations ([#2](https://github.com/frontegg/entitlements-javascript-commons/issues/2)) ([e2e63a7](https://github.com/frontegg/entitlements-javascript-commons/commit/e2e63a74211a723dc326918e42e2093fcca86779))
|
|
32
|
+
|
|
33
|
+
# 1.0.0-alpha.1 (2023-10-03)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
### Bug Fixes
|
|
37
|
+
|
|
38
|
+
* fix alpha version ([4efb4b5](https://github.com/frontegg/entitlements-javascript-commons/commit/4efb4b5e3944caa117f8e1fa5640f3d640018d74))
|
|
39
|
+
* fix changelog ([560b31a](https://github.com/frontegg/entitlements-javascript-commons/commit/560b31a00c2e14c7e0708c88f454363e537ecdce))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
### Features
|
|
43
|
+
|
|
44
|
+
* create pipeline and release infra ([16fd6d1](https://github.com/frontegg/entitlements-javascript-commons/commit/16fd6d165cff4c3ae28e2392db2480d41dd591b1))
|
|
45
|
+
* **conditions:** add condition evaluator ([#5](https://github.com/frontegg/entitlements-javascript-commons/issues/5)) ([5ca2446](https://github.com/frontegg/entitlements-javascript-commons/commit/5ca24465a76b9fa103977e5600a6d870da5520cb))
|
|
46
|
+
* **operations:** add numeric operations ([#3](https://github.com/frontegg/entitlements-javascript-commons/issues/3)) ([#10](https://github.com/frontegg/entitlements-javascript-commons/issues/10)) ([0b3de2f](https://github.com/frontegg/entitlements-javascript-commons/commit/0b3de2f7f1aede036ec63e4fadf898dcf5ad32a4))
|
|
47
|
+
* **operations:** add string operations ([#2](https://github.com/frontegg/entitlements-javascript-commons/issues/2)) ([e2e63a7](https://github.com/frontegg/entitlements-javascript-commons/commit/e2e63a74211a723dc326918e42e2093fcca86779))
|
|
48
|
+
|
|
49
|
+
# 1.0.0-alpha.5 (2023-10-03)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
### Bug Fixes
|
|
53
|
+
|
|
54
|
+
* fix alpha version ([4efb4b5](https://github.com/frontegg/entitlements-javascript-commons/commit/4efb4b5e3944caa117f8e1fa5640f3d640018d74))
|
|
55
|
+
|
|
56
|
+
# 1.0.0-alpha.4 (2023-10-03)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
### Features
|
|
60
|
+
|
|
61
|
+
* **conditions:** add condition evaluator ([#5](https://github.com/frontegg/entitlements-javascript-commons/issues/5)) ([5ca2446](https://github.com/frontegg/entitlements-javascript-commons/commit/5ca24465a76b9fa103977e5600a6d870da5520cb))
|
|
62
|
+
|
|
63
|
+
# 1.0.0-alpha.3 (2023-10-03)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
### Features
|
|
67
|
+
|
|
68
|
+
* **operations:** add numeric operations ([#3](https://github.com/frontegg/entitlements-javascript-commons/issues/3)) ([#10](https://github.com/frontegg/entitlements-javascript-commons/issues/10)) ([0b3de2f](https://github.com/frontegg/entitlements-javascript-commons/commit/0b3de2f7f1aede036ec63e4fadf898dcf5ad32a4))
|
|
69
|
+
|
|
70
|
+
# 1.0.0-alpha.2 (2023-10-03)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
### Features
|
|
74
|
+
|
|
75
|
+
* **operations:** add string operations ([#2](https://github.com/frontegg/entitlements-javascript-commons/issues/2)) ([e2e63a7](https://github.com/frontegg/entitlements-javascript-commons/commit/e2e63a74211a723dc326918e42e2093fcca86779))
|
|
76
|
+
|
|
1
77
|
# 1.0.0-alpha.1 (2023-10-03)
|
|
2
78
|
|
|
3
79
|
|
package/jest.config.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
transform: { '^.+\\.ts?$': 'ts-jest' },
|
|
3
|
+
testEnvironment: 'node',
|
|
4
|
+
testRegex: 'src/.*\\.(test|spec)?\\.(ts|tsx)$',
|
|
5
|
+
moduleFileExtensions: ['ts', 'js', 'json', 'node'],
|
|
6
|
+
rootDir: '.',
|
|
7
|
+
collectCoverageFrom: ['src/**/*.{js,ts}', '!**/node_modules/**', '!**/dist/**', '!**/vendor/**'],
|
|
8
|
+
coverageThreshold: {
|
|
9
|
+
global: {
|
|
10
|
+
statements: 17,
|
|
11
|
+
branches: 24,
|
|
12
|
+
functions: 20,
|
|
13
|
+
lines: 18,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
reporters: [
|
|
17
|
+
'default',
|
|
18
|
+
[
|
|
19
|
+
'jest-junit',
|
|
20
|
+
{
|
|
21
|
+
outputDirectory: 'test-results',
|
|
22
|
+
outputName: 'jest-junit.xml',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
],
|
|
26
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontegg/entitlements-javascript-commons",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,8 +21,8 @@
|
|
|
21
21
|
"author": "Frontegg",
|
|
22
22
|
"license": "ISC",
|
|
23
23
|
"homepage": "https://github.com/frontegg/entitlements-javascript-commons",
|
|
24
|
-
"dependencies": {},
|
|
25
24
|
"devDependencies": {
|
|
25
|
+
"@fast-check/jest": "^1.7.3",
|
|
26
26
|
"@semantic-release/changelog": "^6.0.1",
|
|
27
27
|
"@semantic-release/git": "^10.0.1",
|
|
28
28
|
"@types/jest": "^29.2.0",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Condition } from './types';
|
|
2
|
+
import { useOperation } from '../operations/components/operation.resolver';
|
|
3
|
+
|
|
4
|
+
export interface CreateConditionEvaluatorPayload {
|
|
5
|
+
condition: Condition;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type ConditionEvaluator = (attributes: Record<string, any>) => boolean;
|
|
9
|
+
|
|
10
|
+
export function createConditionEvaluator(payload: CreateConditionEvaluatorPayload): ConditionEvaluator {
|
|
11
|
+
const operation = useOperation(payload.condition.op, payload.condition.value);
|
|
12
|
+
|
|
13
|
+
return (attributes: Record<string, any>) => {
|
|
14
|
+
const attributeKey = payload.condition.attribute;
|
|
15
|
+
const value = attributes[attributeKey];
|
|
16
|
+
const { isValid: result } = value !== undefined && operation ? operation(value) : { isValid: false };
|
|
17
|
+
|
|
18
|
+
return payload.condition.negate ? !result : result;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { createConditionEvaluator } from '../condition.evaluator';
|
|
2
|
+
import { OperationEnum } from '../../operations/types';
|
|
3
|
+
|
|
4
|
+
describe('ConditionEvaluator', () => {
|
|
5
|
+
it('should return false when condition operation is undefined', () => {
|
|
6
|
+
const conditionEvaluator = createConditionEvaluator({
|
|
7
|
+
condition: { op: 'not supported' as any, value: { string: 'test' }, negate: false, attribute: 'test' },
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
expect(conditionEvaluator({})).toEqual(false);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('should return false when attribute value is undefined', () => {
|
|
14
|
+
const attributeKey = 'vendorId';
|
|
15
|
+
const attributes = { [attributeKey]: undefined };
|
|
16
|
+
const conditionEvaluator = createConditionEvaluator({
|
|
17
|
+
condition: { op: OperationEnum.Contains, value: { list: ['test'] }, negate: false, attribute: attributeKey },
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
expect(conditionEvaluator(attributes)).toEqual(false);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('should return false when attribute fails operation validation', () => {
|
|
24
|
+
const attributeKey = 'vendorId';
|
|
25
|
+
const attributes = { [attributeKey]: 'Vendor' };
|
|
26
|
+
const conditionEvaluator = createConditionEvaluator({
|
|
27
|
+
condition: { op: OperationEnum.Contains, value: { list: ['test'] }, negate: false, attribute: attributeKey },
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
expect(conditionEvaluator(attributes)).toEqual(false);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should return true when attribute succeeds operation validation', () => {
|
|
34
|
+
const attributeKey = 'vendorId';
|
|
35
|
+
const attributes = { [attributeKey]: 'testVendor' };
|
|
36
|
+
const conditionEvaluator = createConditionEvaluator({
|
|
37
|
+
condition: { op: OperationEnum.Contains, value: { list: ['test'] }, negate: false, attribute: attributeKey },
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
expect(conditionEvaluator(attributes)).toEqual(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('should return false when attribute succeeds operation validation with negate', () => {
|
|
44
|
+
const attributeKey = 'vendorId';
|
|
45
|
+
const attributes = { [attributeKey]: 'testVendor' };
|
|
46
|
+
const conditionEvaluator = createConditionEvaluator({
|
|
47
|
+
condition: { op: OperationEnum.Contains, value: { list: ['test'] }, negate: true, attribute: attributeKey },
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
expect(conditionEvaluator(attributes)).toEqual(false);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { OperationEnum, OperationsMapper } from '../types';
|
|
2
|
+
import { useIsOperation } from './operations';
|
|
3
|
+
import { BooleanOperationPayload } from './types';
|
|
4
|
+
|
|
5
|
+
export * from './operations';
|
|
6
|
+
|
|
7
|
+
export const BooleanOperationsMapper: OperationsMapper = {
|
|
8
|
+
[OperationEnum.Is]: (value) => useIsOperation(value as BooleanOperationPayload),
|
|
9
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { fc, test } from '@fast-check/jest';
|
|
2
|
+
import { useIsOperation } from './operations';
|
|
3
|
+
|
|
4
|
+
describe('Boolean operations', () => {
|
|
5
|
+
test.prop([fc.boolean(), fc.boolean()], { verbose: true })(
|
|
6
|
+
'should return correct validity if the boolean values equal',
|
|
7
|
+
(boolean, attribute) => {
|
|
8
|
+
const result = useIsOperation({ boolean })(attribute);
|
|
9
|
+
|
|
10
|
+
return (attribute === boolean) === result.isValid;
|
|
11
|
+
},
|
|
12
|
+
);
|
|
13
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ConditionValue, OperationEnum, OperationHandler, OperationsMapper } from '../types';
|
|
2
|
+
import { StringOperationsMapper } from '../string';
|
|
3
|
+
import { NumericOperationsMapper } from '../numeric';
|
|
4
|
+
import { DateOperationsMapper } from '../date';
|
|
5
|
+
import { BooleanOperationsMapper } from '../boolean';
|
|
6
|
+
|
|
7
|
+
const operationEnrichersMapper: OperationsMapper = {
|
|
8
|
+
...StringOperationsMapper,
|
|
9
|
+
...NumericOperationsMapper,
|
|
10
|
+
...DateOperationsMapper,
|
|
11
|
+
...BooleanOperationsMapper,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function useOperation(operation: OperationEnum, value: ConditionValue): OperationHandler | undefined {
|
|
15
|
+
const operationContextEnricher = operationEnrichersMapper[operation];
|
|
16
|
+
|
|
17
|
+
return operationContextEnricher ? operationContextEnricher(value) : undefined;
|
|
18
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { useOperation } from '../operation.resolver';
|
|
2
|
+
|
|
3
|
+
describe('OperationResolver', () => {
|
|
4
|
+
it('should return undefined when operation is not supported', () => {
|
|
5
|
+
const operation = useOperation('not supported' as any, { string: 'test' });
|
|
6
|
+
expect(operation).toEqual(undefined);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { OperationEnum, OperationsMapper } from '../types';
|
|
2
|
+
import {
|
|
3
|
+
useDateBetweenOperation,
|
|
4
|
+
useDateOnOperation,
|
|
5
|
+
useDateOnOrAfterOperation,
|
|
6
|
+
useDateOnOrBeforeOperation,
|
|
7
|
+
} from './operations';
|
|
8
|
+
import { BetweenDateOperationPayload, SingleDateOperationPayload } from './types';
|
|
9
|
+
|
|
10
|
+
export * from './operations';
|
|
11
|
+
export * from './types';
|
|
12
|
+
|
|
13
|
+
export const DateOperationsMapper: OperationsMapper = {
|
|
14
|
+
[OperationEnum.On]: (value) => useDateOnOperation(value as SingleDateOperationPayload),
|
|
15
|
+
[OperationEnum.OnOrAfter]: (value) => useDateOnOrAfterOperation(value as SingleDateOperationPayload),
|
|
16
|
+
[OperationEnum.OnOrBefore]: (value) => useDateOnOrBeforeOperation(value as SingleDateOperationPayload),
|
|
17
|
+
[OperationEnum.BetweenDate]: (value) => useDateBetweenOperation(value as BetweenDateOperationPayload),
|
|
18
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useDateBetweenOperation,
|
|
3
|
+
useDateOnOperation,
|
|
4
|
+
useDateOnOrAfterOperation,
|
|
5
|
+
useDateOnOrBeforeOperation,
|
|
6
|
+
} from './index';
|
|
7
|
+
import { fc, test } from '@fast-check/jest';
|
|
8
|
+
|
|
9
|
+
describe('Date operations', () => {
|
|
10
|
+
test.prop([fc.date(), fc.date()], { verbose: true })(
|
|
11
|
+
'should return correct validity if the dates equal',
|
|
12
|
+
(date, attribute) => {
|
|
13
|
+
const result = useDateOnOperation({ date })(attribute);
|
|
14
|
+
|
|
15
|
+
return (date.getTime() === attribute.getTime()) === result.isValid;
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
test.prop([fc.date(), fc.date()], { verbose: true })(
|
|
20
|
+
'should return correct validity for on or after validation for given dates',
|
|
21
|
+
(date, attribute) => {
|
|
22
|
+
const result = useDateOnOrAfterOperation({ date })(attribute);
|
|
23
|
+
|
|
24
|
+
return attribute.getTime() >= date.getTime() === result.isValid;
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
test.prop([fc.date(), fc.date()], { verbose: true })(
|
|
29
|
+
'should return correct validity for on or before validation for given dates',
|
|
30
|
+
(date, attribute) => {
|
|
31
|
+
const result = useDateOnOrBeforeOperation({ date })(attribute);
|
|
32
|
+
|
|
33
|
+
return attribute.getTime() <= date.getTime() === result.isValid;
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
test.prop([fc.date(), fc.date(), fc.date()], { verbose: true })(
|
|
38
|
+
'should return correct validity for range validation for given dates',
|
|
39
|
+
(start, end, attribute) => {
|
|
40
|
+
const result = useDateBetweenOperation({ start, end })(attribute);
|
|
41
|
+
|
|
42
|
+
return (attribute.getTime() >= start.getTime() && attribute.getTime() <= end.getTime()) === result.isValid;
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OperationHandler } from '../types';
|
|
2
|
+
import { BetweenDateOperationPayload, SingleDateOperationPayload } from './types';
|
|
3
|
+
|
|
4
|
+
export function useDateOnOperation(payload: SingleDateOperationPayload): OperationHandler {
|
|
5
|
+
return (attribute: Date) => ({ isValid: attribute.getTime() === payload.date.getTime() });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useDateOnOrAfterOperation(payload: SingleDateOperationPayload): OperationHandler {
|
|
9
|
+
return (attribute: Date) => ({ isValid: attribute.getTime() >= payload.date.getTime() });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useDateOnOrBeforeOperation(payload: SingleDateOperationPayload): OperationHandler {
|
|
13
|
+
return (attribute: Date) => ({ isValid: attribute.getTime() <= payload.date.getTime() });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useDateBetweenOperation(payload: BetweenDateOperationPayload): OperationHandler {
|
|
17
|
+
return (attribute: Date) => ({
|
|
18
|
+
isValid: attribute.getTime() >= payload.start.getTime() && attribute.getTime() <= payload.end.getTime(),
|
|
19
|
+
});
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OperationEnum, OperationsMapper } from '../types';
|
|
2
|
+
import {
|
|
3
|
+
useBetweenNumericOperation,
|
|
4
|
+
useEqualsOperation,
|
|
5
|
+
useGreaterThanEqualOperation,
|
|
6
|
+
useGreaterThanOperation,
|
|
7
|
+
useLesserThanEqualOperation,
|
|
8
|
+
useLesserThanOperation,
|
|
9
|
+
} from './operations';
|
|
10
|
+
import { BetweenNumericOperationPayload, SingleNumericOperationPayload } from './types';
|
|
11
|
+
|
|
12
|
+
export * from './operations';
|
|
13
|
+
export * from './types';
|
|
14
|
+
|
|
15
|
+
export const NumericOperationsMapper: OperationsMapper = {
|
|
16
|
+
[OperationEnum.Equal]: (value) => useEqualsOperation(value as SingleNumericOperationPayload),
|
|
17
|
+
[OperationEnum.GreaterThan]: (value) => useGreaterThanOperation(value as SingleNumericOperationPayload),
|
|
18
|
+
[OperationEnum.GreaterThanEqual]: (value) => useGreaterThanEqualOperation(value as SingleNumericOperationPayload),
|
|
19
|
+
[OperationEnum.LesserThan]: (value) => useLesserThanOperation(value as SingleNumericOperationPayload),
|
|
20
|
+
[OperationEnum.LesserThanEqual]: (value) => useLesserThanEqualOperation(value as SingleNumericOperationPayload),
|
|
21
|
+
[OperationEnum.BetweenNumeric]: (value) => useBetweenNumericOperation(value as BetweenNumericOperationPayload),
|
|
22
|
+
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useBetweenNumericOperation,
|
|
3
|
+
useEqualsOperation,
|
|
4
|
+
useGreaterThanEqualOperation,
|
|
5
|
+
useGreaterThanOperation,
|
|
6
|
+
useLesserThanOperation,
|
|
7
|
+
} from './index';
|
|
8
|
+
import { fc, test } from '@fast-check/jest';
|
|
9
|
+
|
|
10
|
+
describe('Numeric operations', () => {
|
|
11
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
12
|
+
'should return true if the numbers equal',
|
|
13
|
+
(number, attribute) => {
|
|
14
|
+
const result = useEqualsOperation({ number })(attribute);
|
|
15
|
+
return (number === attribute) === result.isValid;
|
|
16
|
+
},
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
20
|
+
'should return correct validity value when attribute is greater than the given number',
|
|
21
|
+
(number, attribute) => {
|
|
22
|
+
const result = useGreaterThanOperation({ number })(attribute);
|
|
23
|
+
|
|
24
|
+
return attribute > number === result.isValid;
|
|
25
|
+
},
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
29
|
+
'should return correct validity value when attribute is greater or equals to given number',
|
|
30
|
+
(number, attribute) => {
|
|
31
|
+
const result = useGreaterThanEqualOperation({ number })(attribute);
|
|
32
|
+
|
|
33
|
+
return attribute >= number === result.isValid;
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
38
|
+
'should return correct validity value when attribute is lesser than then given number',
|
|
39
|
+
(number, attribute) => {
|
|
40
|
+
const result = useLesserThanOperation({ number })(attribute);
|
|
41
|
+
|
|
42
|
+
return attribute < number === result.isValid;
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
47
|
+
'should return correct validity value when attribute is lesser or equals to given number',
|
|
48
|
+
(number, attribute) => {
|
|
49
|
+
const result = useLesserThanOperation({ number })(attribute);
|
|
50
|
+
|
|
51
|
+
return attribute <= number === result.isValid;
|
|
52
|
+
},
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
test.prop([fc.integer(), fc.integer(), fc.integer()], { verbose: true })(
|
|
56
|
+
'should return correct validity value when attribute is between the given numbers',
|
|
57
|
+
(start, end, attribute) => {
|
|
58
|
+
const result = useBetweenNumericOperation({ start, end })(attribute);
|
|
59
|
+
|
|
60
|
+
return (attribute >= start && attribute <= end) === result.isValid;
|
|
61
|
+
},
|
|
62
|
+
);
|
|
63
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { OperationHandler } from '../types';
|
|
2
|
+
import { BetweenNumericOperationPayload, SingleNumericOperationPayload } from './types';
|
|
3
|
+
|
|
4
|
+
export function useEqualsOperation(payload: SingleNumericOperationPayload): OperationHandler {
|
|
5
|
+
return (attribute: number) => ({ isValid: attribute === payload.number });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useGreaterThanOperation(payload: SingleNumericOperationPayload): OperationHandler {
|
|
9
|
+
return (attribute: number) => ({ isValid: attribute > payload.number });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useGreaterThanEqualOperation(payload: SingleNumericOperationPayload): OperationHandler {
|
|
13
|
+
return (attribute: number) => ({ isValid: attribute >= payload.number });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useLesserThanOperation(payload: SingleNumericOperationPayload): OperationHandler {
|
|
17
|
+
return (attribute: number) => ({ isValid: attribute < payload.number });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useLesserThanEqualOperation(payload: SingleNumericOperationPayload): OperationHandler {
|
|
21
|
+
return (attribute: number) => ({ isValid: attribute <= payload.number });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function useBetweenNumericOperation(payload: BetweenNumericOperationPayload): OperationHandler {
|
|
25
|
+
return (attribute: number) => ({ isValid: attribute >= payload.start && attribute <= payload.end });
|
|
26
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useBetweenNumericOperation,
|
|
3
|
+
useEqualsOperation,
|
|
4
|
+
useGreaterThanEqualOperation,
|
|
5
|
+
useGreaterThanOperation,
|
|
6
|
+
useLesserThanEqualOperation,
|
|
7
|
+
useLesserThanOperation,
|
|
8
|
+
} from '../index';
|
|
9
|
+
import { fc, test } from '@fast-check/jest';
|
|
10
|
+
|
|
11
|
+
describe('Numeric operations', () => {
|
|
12
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
13
|
+
'should return true if the numbers equal',
|
|
14
|
+
(number, attribute) => {
|
|
15
|
+
const result = useEqualsOperation({ number })(attribute);
|
|
16
|
+
return (number === attribute) === result.isValid;
|
|
17
|
+
},
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
21
|
+
'should return correct validity value when attribute is greater than the given number',
|
|
22
|
+
(number, attribute) => {
|
|
23
|
+
const result = useGreaterThanOperation({ number })(attribute);
|
|
24
|
+
|
|
25
|
+
return attribute > number === result.isValid;
|
|
26
|
+
},
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
30
|
+
'should return correct validity value when attribute is greater or equals to given number',
|
|
31
|
+
(number, attribute) => {
|
|
32
|
+
const result = useGreaterThanEqualOperation({ number })(attribute);
|
|
33
|
+
|
|
34
|
+
return attribute >= number === result.isValid;
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
39
|
+
'should return correct validity value when attribute is lesser than then given number',
|
|
40
|
+
(number, attribute) => {
|
|
41
|
+
const result = useLesserThanOperation({ number })(attribute);
|
|
42
|
+
|
|
43
|
+
return attribute < number === result.isValid;
|
|
44
|
+
},
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
test.prop([fc.integer(), fc.integer()], { verbose: true })(
|
|
48
|
+
'should return correct validity value when attribute is lesser or equals to given number',
|
|
49
|
+
(number, attribute) => {
|
|
50
|
+
const result = useLesserThanEqualOperation({ number })(attribute);
|
|
51
|
+
|
|
52
|
+
return attribute <= number === result.isValid;
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
test.prop([fc.integer(), fc.integer(), fc.integer()], { verbose: true })(
|
|
57
|
+
'should return correct validity value when attribute is between the given numbers',
|
|
58
|
+
(start, end, attribute) => {
|
|
59
|
+
const result = useBetweenNumericOperation({ start, end })(attribute);
|
|
60
|
+
|
|
61
|
+
return (attribute >= start && attribute <= end) === result.isValid;
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface SingleNumericOperationPayload {
|
|
2
|
+
number: number;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export interface BetweenNumericOperationPayload {
|
|
6
|
+
start: number;
|
|
7
|
+
end: number;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type NumericOperationPayload = SingleNumericOperationPayload | BetweenNumericOperationPayload;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { OperationEnum, OperationsMapper } from '../types';
|
|
2
|
+
import {
|
|
3
|
+
useContainsOperation,
|
|
4
|
+
useEndsWithOperation,
|
|
5
|
+
useInListOperation,
|
|
6
|
+
useMatchesOperation,
|
|
7
|
+
useStartsWithOperation,
|
|
8
|
+
} from './operations';
|
|
9
|
+
import { ListStringOperationPayload, SingleStringOperationPayload } from './types';
|
|
10
|
+
|
|
11
|
+
export * from './operations';
|
|
12
|
+
export * from './types';
|
|
13
|
+
|
|
14
|
+
export const StringOperationsMapper: OperationsMapper = {
|
|
15
|
+
[OperationEnum.StartsWith]: (value) => useStartsWithOperation(value as ListStringOperationPayload),
|
|
16
|
+
[OperationEnum.EndsWith]: (value) => useEndsWithOperation(value as ListStringOperationPayload),
|
|
17
|
+
[OperationEnum.Contains]: (value) => useContainsOperation(value as ListStringOperationPayload),
|
|
18
|
+
[OperationEnum.InList]: (value) => useInListOperation(value as ListStringOperationPayload),
|
|
19
|
+
[OperationEnum.Matches]: (value) => useMatchesOperation(value as SingleStringOperationPayload),
|
|
20
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useContainsOperation, useEndsWithOperation, useInListOperation, useStartsWithOperation } from './index';
|
|
2
|
+
import { fc, test } from '@fast-check/jest';
|
|
3
|
+
|
|
4
|
+
describe('String operations', () => {
|
|
5
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
6
|
+
'should return correct validity if one of the strings starts with the given prefix',
|
|
7
|
+
(list, prefix) => {
|
|
8
|
+
const result = useStartsWithOperation({ list })(prefix);
|
|
9
|
+
return list.some((value) => prefix.startsWith(value)) === result.isValid;
|
|
10
|
+
},
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
14
|
+
'should return correct validity if one of the strings ends with the given suffix',
|
|
15
|
+
(list, suffix) => {
|
|
16
|
+
const result = useEndsWithOperation({ list })(suffix);
|
|
17
|
+
return list.some((value) => suffix.endsWith(value)) === result.isValid;
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
22
|
+
'should return correct validity if one of the strings contains with given attribute',
|
|
23
|
+
(list, included) => {
|
|
24
|
+
const result = useContainsOperation({ list })(included);
|
|
25
|
+
|
|
26
|
+
return list.some((value) => included.includes(value)) === result.isValid;
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
31
|
+
'should return correct validity if one of the strings is in the given list',
|
|
32
|
+
(list, string) => {
|
|
33
|
+
const result = useInListOperation({ list })(string);
|
|
34
|
+
|
|
35
|
+
return list.includes(string) === result.isValid;
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { OperationHandler } from '../types';
|
|
2
|
+
import { ListStringOperationPayload, SingleStringOperationPayload } from './types';
|
|
3
|
+
|
|
4
|
+
export function useStartsWithOperation(payload: ListStringOperationPayload): OperationHandler {
|
|
5
|
+
return (attribute: string) => ({ isValid: payload.list.some((value) => attribute.startsWith(value)) });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function useEndsWithOperation(payload: ListStringOperationPayload): OperationHandler {
|
|
9
|
+
return (attribute: string) => ({ isValid: payload.list.some((value) => attribute.endsWith(value)) });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useContainsOperation(payload: ListStringOperationPayload): OperationHandler {
|
|
13
|
+
return (attribute: string) => ({ isValid: payload.list.some((value) => attribute.includes(value)) });
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function useInListOperation(payload: ListStringOperationPayload): OperationHandler {
|
|
17
|
+
return (attribute: string) => ({ isValid: payload.list.includes(attribute) });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function useMatchesOperation(payload: SingleStringOperationPayload): OperationHandler {
|
|
21
|
+
return (attribute: string) => {
|
|
22
|
+
let expression: RegExp;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
expression = new RegExp(payload.string);
|
|
26
|
+
} catch (e) {
|
|
27
|
+
return { isValid: false };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return { isValid: expression.test(attribute) };
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useContainsOperation, useEndsWithOperation, useInListOperation, useStartsWithOperation } from '../index';
|
|
2
|
+
import { fc, test } from '@fast-check/jest';
|
|
3
|
+
|
|
4
|
+
describe('String operations', () => {
|
|
5
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
6
|
+
'should return correct validity if one of the strings starts with the given prefix',
|
|
7
|
+
(list, prefix) => {
|
|
8
|
+
const result = useStartsWithOperation({ list })(prefix);
|
|
9
|
+
return list.some((value) => prefix.startsWith(value)) === result.isValid;
|
|
10
|
+
},
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
14
|
+
'should return correct validity if one of the strings ends with the given suffix',
|
|
15
|
+
(list, suffix) => {
|
|
16
|
+
const result = useEndsWithOperation({ list })(suffix);
|
|
17
|
+
return list.some((value) => suffix.endsWith(value)) === result.isValid;
|
|
18
|
+
},
|
|
19
|
+
);
|
|
20
|
+
|
|
21
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
22
|
+
'should return correct validity if one of the strings contains with given attribute',
|
|
23
|
+
(list, included) => {
|
|
24
|
+
const result = useContainsOperation({ list })(included);
|
|
25
|
+
|
|
26
|
+
return list.some((value) => included.includes(value)) === result.isValid;
|
|
27
|
+
},
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
test.prop([fc.array(fc.string()), fc.string()], { verbose: true })(
|
|
31
|
+
'should return correct validity if one of the strings is in the given list',
|
|
32
|
+
(list, string) => {
|
|
33
|
+
const result = useInListOperation({ list })(string);
|
|
34
|
+
|
|
35
|
+
return list.includes(string) === result.isValid;
|
|
36
|
+
},
|
|
37
|
+
);
|
|
38
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { OperationEnum } from './operation.enum';
|
|
2
|
+
import { StringOperationPayload } from '../string';
|
|
3
|
+
import { NumericOperationPayload } from '../numeric';
|
|
4
|
+
import { DateOperationPayload } from '../date';
|
|
5
|
+
import { BooleanOperationPayload } from '../boolean/types';
|
|
6
|
+
|
|
7
|
+
export interface OperationResult {
|
|
8
|
+
isValid: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type ConditionValue =
|
|
12
|
+
| StringOperationPayload
|
|
13
|
+
| NumericOperationPayload
|
|
14
|
+
| DateOperationPayload
|
|
15
|
+
| BooleanOperationPayload;
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
18
|
+
export type OperationHandler = (attribute: any) => OperationResult;
|
|
19
|
+
export type OperationContextEnricher = (value: ConditionValue) => OperationHandler;
|
|
20
|
+
export type OperationsMapper = { [key in OperationEnum]?: OperationContextEnricher };
|
|
21
|
+
|
|
22
|
+
export * from './operation.enum';
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export enum OperationEnum {
|
|
2
|
+
// String Operations
|
|
3
|
+
InList = 'in_list',
|
|
4
|
+
StartsWith = 'starts_with',
|
|
5
|
+
EndsWith = 'ends_with',
|
|
6
|
+
Contains = 'contains',
|
|
7
|
+
Matches = 'matches',
|
|
8
|
+
|
|
9
|
+
// Numeric Operations
|
|
10
|
+
Equal = 'equal',
|
|
11
|
+
GreaterThan = 'greater_than',
|
|
12
|
+
GreaterThanEqual = 'greater_than_equal',
|
|
13
|
+
LesserThan = 'lower_than',
|
|
14
|
+
LesserThanEqual = 'lower_than_equal',
|
|
15
|
+
BetweenNumeric = 'between_numeric',
|
|
16
|
+
|
|
17
|
+
// Boolean Operations
|
|
18
|
+
Is = 'is',
|
|
19
|
+
|
|
20
|
+
// Date Operations
|
|
21
|
+
On = 'on',
|
|
22
|
+
BetweenDate = 'between_date',
|
|
23
|
+
OnOrAfter = 'on_or_after',
|
|
24
|
+
OnOrBefore = 'on_or_before',
|
|
25
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { CreateRuleEvaluatorPayload, RuleEvaluationResultEnum, RuleEvaluator } from './types';
|
|
2
|
+
import { createConditionEvaluator } from '../conditions';
|
|
3
|
+
|
|
4
|
+
export function createRuleEvaluator(payload: CreateRuleEvaluatorPayload): RuleEvaluator {
|
|
5
|
+
return (attributes: Record<string, any>): RuleEvaluationResultEnum => {
|
|
6
|
+
const isRuleTreatable = payload.rule.conditions.every((condition) => {
|
|
7
|
+
const evaluator = createConditionEvaluator({ condition });
|
|
8
|
+
|
|
9
|
+
return evaluator(attributes);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
return isRuleTreatable ? RuleEvaluationResultEnum.Treatable : RuleEvaluationResultEnum.Insufficient;
|
|
13
|
+
};
|
|
14
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { ConditionLogicEnum, Rule, RuleEvaluationResultEnum, TreatmentEnum } from '../types';
|
|
2
|
+
import { createRuleEvaluator } from '../rule.evaluator';
|
|
3
|
+
import { OperationEnum } from '../../operations/types';
|
|
4
|
+
|
|
5
|
+
describe('RuleEvaluator', () => {
|
|
6
|
+
it('should return RuleEvaluationResultEnum.Insufficient when conditions are invalid', () => {
|
|
7
|
+
const rule: Rule = {
|
|
8
|
+
treatment: TreatmentEnum.True,
|
|
9
|
+
description: 'test',
|
|
10
|
+
conditions: [
|
|
11
|
+
{
|
|
12
|
+
attribute: 'test',
|
|
13
|
+
op: 'not supported' as any,
|
|
14
|
+
value: { list: ['test'] },
|
|
15
|
+
negate: false,
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
conditionLogic: ConditionLogicEnum.And,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const ruleEvaluator = createRuleEvaluator({ rule });
|
|
22
|
+
const result = ruleEvaluator({});
|
|
23
|
+
|
|
24
|
+
expect(result).toEqual(RuleEvaluationResultEnum.Insufficient);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return RuleEvaluationResultEnum.Insufficient when no (valid) condition passes validation', () => {
|
|
28
|
+
const rule: Rule = {
|
|
29
|
+
treatment: TreatmentEnum.True,
|
|
30
|
+
description: 'test',
|
|
31
|
+
conditions: [
|
|
32
|
+
{
|
|
33
|
+
attribute: 'attribute',
|
|
34
|
+
op: OperationEnum.StartsWith,
|
|
35
|
+
value: { list: ['test'] },
|
|
36
|
+
negate: false,
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
attribute: 'attribute',
|
|
40
|
+
op: OperationEnum.Contains,
|
|
41
|
+
value: { list: ['test'] },
|
|
42
|
+
negate: false,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
conditionLogic: ConditionLogicEnum.And,
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const ruleEvaluator = createRuleEvaluator({ rule });
|
|
49
|
+
const result = ruleEvaluator({ attribute: '1' });
|
|
50
|
+
|
|
51
|
+
expect(result).toEqual(RuleEvaluationResultEnum.Insufficient);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should return RuleEvaluationResultEnum.Insufficient when some (valid) conditions pass validation', () => {
|
|
55
|
+
const rule: Rule = {
|
|
56
|
+
treatment: TreatmentEnum.True,
|
|
57
|
+
description: 'test',
|
|
58
|
+
conditions: [
|
|
59
|
+
{
|
|
60
|
+
attribute: 'attribute',
|
|
61
|
+
op: OperationEnum.StartsWith,
|
|
62
|
+
value: { list: ['test'] },
|
|
63
|
+
negate: false,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
attribute: 'attribute',
|
|
67
|
+
op: OperationEnum.Contains,
|
|
68
|
+
value: { list: ['test'] },
|
|
69
|
+
negate: false,
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
conditionLogic: ConditionLogicEnum.And,
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const ruleEvaluator = createRuleEvaluator({ rule });
|
|
76
|
+
const result = ruleEvaluator({ attribute: 'te' });
|
|
77
|
+
|
|
78
|
+
expect(result).toEqual(RuleEvaluationResultEnum.Insufficient);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should return RuleEvaluationResultEnum.Treatable when all (valid) conditions pass validation', () => {
|
|
82
|
+
const rule: Rule = {
|
|
83
|
+
treatment: TreatmentEnum.True,
|
|
84
|
+
description: 'test',
|
|
85
|
+
conditions: [
|
|
86
|
+
{
|
|
87
|
+
attribute: 'attribute',
|
|
88
|
+
op: OperationEnum.InList,
|
|
89
|
+
value: { list: ['test'] },
|
|
90
|
+
negate: false,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
attribute: 'attribute',
|
|
94
|
+
op: OperationEnum.Contains,
|
|
95
|
+
value: { list: ['test'] },
|
|
96
|
+
negate: false,
|
|
97
|
+
},
|
|
98
|
+
],
|
|
99
|
+
conditionLogic: ConditionLogicEnum.And,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const ruleEvaluator = createRuleEvaluator({ rule });
|
|
103
|
+
const result = ruleEvaluator({ attribute: 'test' });
|
|
104
|
+
|
|
105
|
+
expect(result).toEqual(RuleEvaluationResultEnum.Treatable);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('should return RuleEvaluationResultEnum.Treatable when all (valid) conditions pass validation', () => {
|
|
109
|
+
const rule: Rule = {
|
|
110
|
+
treatment: TreatmentEnum.True,
|
|
111
|
+
description: 'test',
|
|
112
|
+
conditions: [
|
|
113
|
+
{
|
|
114
|
+
attribute: 'attribute',
|
|
115
|
+
op: OperationEnum.InList,
|
|
116
|
+
value: { list: ['test'] },
|
|
117
|
+
negate: false,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
attribute: 'attribute',
|
|
121
|
+
op: OperationEnum.Contains,
|
|
122
|
+
value: { list: ['not'] },
|
|
123
|
+
negate: true,
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
attribute: 'numeric',
|
|
127
|
+
op: OperationEnum.Equal,
|
|
128
|
+
value: { number: 2 },
|
|
129
|
+
negate: false,
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
conditionLogic: ConditionLogicEnum.And,
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const ruleEvaluator = createRuleEvaluator({ rule });
|
|
136
|
+
const result = ruleEvaluator({ attribute: 'test', numeric: 2 });
|
|
137
|
+
|
|
138
|
+
expect(result).toEqual(RuleEvaluationResultEnum.Treatable);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Condition } from '../conditions';
|
|
2
|
+
|
|
3
|
+
export interface Rule {
|
|
4
|
+
description: string;
|
|
5
|
+
conditionLogic: ConditionLogicEnum.And;
|
|
6
|
+
conditions: Condition[];
|
|
7
|
+
treatment: TreatmentEnum;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export enum ConditionLogicEnum {
|
|
11
|
+
And = 'and',
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export enum TreatmentEnum {
|
|
15
|
+
True = 'true',
|
|
16
|
+
False = 'false',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export enum RuleEvaluationResultEnum {
|
|
20
|
+
Treatable = 'treatable',
|
|
21
|
+
Insufficient = 'insufficient',
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CreateRuleEvaluatorPayload {
|
|
25
|
+
rule: Rule;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type RuleEvaluator = (attributes: Record<string, any>) => RuleEvaluationResultEnum;
|