@f-o-t/rules-engine 3.0.0 → 3.0.5

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.
Files changed (105) hide show
  1. package/dist/analyzer/analysis.d.ts +72 -0
  2. package/dist/analyzer/analysis.d.ts.map +1 -0
  3. package/dist/builder/conditions.d.ts +29 -0
  4. package/dist/builder/conditions.d.ts.map +1 -0
  5. package/dist/builder/rule.d.ts +40 -0
  6. package/dist/builder/rule.d.ts.map +1 -0
  7. package/dist/cache/cache.d.ts +21 -0
  8. package/dist/cache/cache.d.ts.map +1 -0
  9. package/dist/cache/noop.d.ts +3 -0
  10. package/dist/cache/noop.d.ts.map +1 -0
  11. package/dist/core/evaluate.d.ts +21 -0
  12. package/dist/core/evaluate.d.ts.map +1 -0
  13. package/dist/core/filter.d.ts +8 -0
  14. package/dist/core/filter.d.ts.map +1 -0
  15. package/dist/core/group.d.ts +10 -0
  16. package/dist/core/group.d.ts.map +1 -0
  17. package/dist/core/sort.d.ts +14 -0
  18. package/dist/core/sort.d.ts.map +1 -0
  19. package/dist/engine/engine.d.ts +27 -0
  20. package/dist/engine/engine.d.ts.map +1 -0
  21. package/dist/engine/hooks.d.ts +16 -0
  22. package/dist/engine/hooks.d.ts.map +1 -0
  23. package/dist/engine/state.d.ts +19 -0
  24. package/dist/engine/state.d.ts.map +1 -0
  25. package/dist/index.d.ts +33 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +3083 -0
  28. package/dist/index.js.map +37 -0
  29. package/dist/optimizer/index-builder.d.ts +48 -0
  30. package/dist/optimizer/index-builder.d.ts.map +1 -0
  31. package/dist/serialization/serializer.d.ts +79 -0
  32. package/dist/serialization/serializer.d.ts.map +1 -0
  33. package/dist/simulation/simulator.d.ts +65 -0
  34. package/dist/simulation/simulator.d.ts.map +1 -0
  35. package/dist/types/config.d.ts +87 -0
  36. package/dist/types/config.d.ts.map +1 -0
  37. package/dist/types/consequence.d.ts +21 -0
  38. package/dist/types/consequence.d.ts.map +1 -0
  39. package/dist/types/evaluation.d.ts +70 -0
  40. package/dist/types/evaluation.d.ts.map +1 -0
  41. package/dist/types/rule.d.ts +114 -0
  42. package/dist/types/rule.d.ts.map +1 -0
  43. package/dist/types/state.d.ts +65 -0
  44. package/dist/types/state.d.ts.map +1 -0
  45. package/dist/utils/conditions.d.ts +24 -0
  46. package/dist/utils/conditions.d.ts.map +1 -0
  47. package/dist/utils/hash.d.ts +3 -0
  48. package/dist/utils/hash.d.ts.map +1 -0
  49. package/dist/utils/id.d.ts +2 -0
  50. package/dist/utils/id.d.ts.map +1 -0
  51. package/dist/utils/time.d.ts +8 -0
  52. package/dist/utils/time.d.ts.map +1 -0
  53. package/dist/validation/conflicts.d.ts +25 -0
  54. package/dist/validation/conflicts.d.ts.map +1 -0
  55. package/dist/validation/integrity.d.ts +38 -0
  56. package/dist/validation/integrity.d.ts.map +1 -0
  57. package/dist/validation/schema.d.ts +69 -0
  58. package/dist/validation/schema.d.ts.map +1 -0
  59. package/dist/versioning/version-store.d.ts +50 -0
  60. package/dist/versioning/version-store.d.ts.map +1 -0
  61. package/package.json +35 -31
  62. package/CHANGELOG.md +0 -168
  63. package/__tests__/builder.test.ts +0 -363
  64. package/__tests__/cache.test.ts +0 -130
  65. package/__tests__/config.test.ts +0 -35
  66. package/__tests__/engine.test.ts +0 -1213
  67. package/__tests__/evaluate.test.ts +0 -339
  68. package/__tests__/exports.test.ts +0 -30
  69. package/__tests__/filter-sort.test.ts +0 -303
  70. package/__tests__/integration.test.ts +0 -419
  71. package/__tests__/money-integration.test.ts +0 -149
  72. package/__tests__/validation.test.ts +0 -862
  73. package/biome.json +0 -39
  74. package/docs/MIGRATION-v3.md +0 -118
  75. package/fot.config.ts +0 -5
  76. package/src/analyzer/analysis.ts +0 -401
  77. package/src/builder/conditions.ts +0 -321
  78. package/src/builder/rule.ts +0 -192
  79. package/src/cache/cache.ts +0 -135
  80. package/src/cache/noop.ts +0 -20
  81. package/src/core/evaluate.ts +0 -185
  82. package/src/core/filter.ts +0 -85
  83. package/src/core/group.ts +0 -103
  84. package/src/core/sort.ts +0 -90
  85. package/src/engine/engine.ts +0 -462
  86. package/src/engine/hooks.ts +0 -235
  87. package/src/engine/state.ts +0 -322
  88. package/src/index.ts +0 -303
  89. package/src/optimizer/index-builder.ts +0 -381
  90. package/src/serialization/serializer.ts +0 -408
  91. package/src/simulation/simulator.ts +0 -359
  92. package/src/types/config.ts +0 -184
  93. package/src/types/consequence.ts +0 -38
  94. package/src/types/evaluation.ts +0 -87
  95. package/src/types/rule.ts +0 -112
  96. package/src/types/state.ts +0 -116
  97. package/src/utils/conditions.ts +0 -108
  98. package/src/utils/hash.ts +0 -30
  99. package/src/utils/id.ts +0 -6
  100. package/src/utils/time.ts +0 -42
  101. package/src/validation/conflicts.ts +0 -440
  102. package/src/validation/integrity.ts +0 -473
  103. package/src/validation/schema.ts +0 -386
  104. package/src/versioning/version-store.ts +0 -337
  105. package/tsconfig.json +0 -29
@@ -1,339 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import type { ConditionGroup } from "@f-o-t/condition-evaluator";
3
- import { createEvaluator, createOperator } from "@f-o-t/condition-evaluator";
4
- import { evaluateRule, evaluateRules } from "../src/core/evaluate";
5
- import type { DefaultConsequences } from "../src/types/consequence";
6
- import type { Rule } from "../src/types/rule";
7
-
8
- type TestRule = Rule<unknown, DefaultConsequences>;
9
-
10
- const createTestRule = (overrides: Partial<TestRule> = {}): TestRule => ({
11
- id: "rule-1",
12
- name: "Test Rule",
13
- description: "A test rule",
14
- conditions: {
15
- id: "group-1",
16
- operator: "AND",
17
- conditions: [
18
- {
19
- id: "cond-1",
20
- type: "number",
21
- field: "amount",
22
- operator: "gt",
23
- value: 100,
24
- },
25
- ],
26
- },
27
- consequences: [
28
- {
29
- type: "set_category",
30
- payload: { categoryId: "high-value" },
31
- },
32
- ],
33
- priority: 10,
34
- enabled: true,
35
- stopOnMatch: false,
36
- tags: ["test"],
37
- category: "finance",
38
- createdAt: new Date(),
39
- updatedAt: new Date(),
40
- ...overrides,
41
- });
42
-
43
- describe("evaluateRule", () => {
44
- test("should match rule when conditions pass", () => {
45
- const rule = createTestRule();
46
- const context = {
47
- data: { amount: 150 },
48
- timestamp: new Date(),
49
- };
50
-
51
- const evaluator = createEvaluator();
52
- const result = evaluateRule(rule, context, evaluator);
53
-
54
- expect(result.matched).toBe(true);
55
- expect(result.ruleId).toBe("rule-1");
56
- expect(result.ruleName).toBe("Test Rule");
57
- expect(result.consequences).toHaveLength(1);
58
- expect(result.consequences[0]?.type).toBe("set_category");
59
- expect(result.skipped).toBe(false);
60
- });
61
-
62
- test("should not match rule when conditions fail", () => {
63
- const rule = createTestRule();
64
- const context = {
65
- data: { amount: 50 },
66
- timestamp: new Date(),
67
- };
68
-
69
- const evaluator = createEvaluator();
70
- const result = evaluateRule(rule, context, evaluator);
71
-
72
- expect(result.matched).toBe(false);
73
- expect(result.consequences).toHaveLength(0);
74
- });
75
-
76
- test("should skip disabled rules when skipDisabled is true", () => {
77
- const rule = createTestRule({ enabled: false });
78
- const context = {
79
- data: { amount: 150 },
80
- timestamp: new Date(),
81
- };
82
-
83
- const evaluator = createEvaluator();
84
- const result = evaluateRule(rule, context, evaluator, { skipDisabled: true });
85
-
86
- expect(result.skipped).toBe(true);
87
- expect(result.skipReason).toBe("Rule is disabled");
88
- expect(result.matched).toBe(false);
89
- });
90
-
91
- test("should evaluate disabled rules when skipDisabled is false", () => {
92
- const rule = createTestRule({ enabled: false });
93
- const context = {
94
- data: { amount: 150 },
95
- timestamp: new Date(),
96
- };
97
-
98
- const evaluator = createEvaluator();
99
- const result = evaluateRule(rule, context, evaluator, { skipDisabled: false });
100
-
101
- expect(result.skipped).toBe(false);
102
- expect(result.matched).toBe(true);
103
- });
104
-
105
- test("should include evaluation time", () => {
106
- const rule = createTestRule();
107
- const context = {
108
- data: { amount: 150 },
109
- timestamp: new Date(),
110
- };
111
-
112
- const evaluator = createEvaluator();
113
- const result = evaluateRule(rule, context, evaluator);
114
-
115
- expect(result.evaluationTimeMs).toBeGreaterThanOrEqual(0);
116
- });
117
-
118
- test("should handle complex condition groups", () => {
119
- const conditions: ConditionGroup = {
120
- id: "group-1",
121
- operator: "AND",
122
- conditions: [
123
- {
124
- id: "cond-1",
125
- type: "number",
126
- field: "amount",
127
- operator: "gt",
128
- value: 100,
129
- },
130
- {
131
- id: "cond-2",
132
- type: "string",
133
- field: "status",
134
- operator: "eq",
135
- value: "active",
136
- },
137
- ],
138
- };
139
-
140
- const rule = createTestRule({ conditions });
141
- const context = {
142
- data: { amount: 150, status: "active" },
143
- timestamp: new Date(),
144
- };
145
-
146
- const evaluator = createEvaluator();
147
- const result = evaluateRule(rule, context, evaluator);
148
-
149
- expect(result.matched).toBe(true);
150
- });
151
-
152
- test("should handle OR condition groups", () => {
153
- const conditions: ConditionGroup = {
154
- id: "group-1",
155
- operator: "OR",
156
- conditions: [
157
- {
158
- id: "cond-1",
159
- type: "number",
160
- field: "amount",
161
- operator: "gt",
162
- value: 1000,
163
- },
164
- {
165
- id: "cond-2",
166
- type: "string",
167
- field: "status",
168
- operator: "eq",
169
- value: "premium",
170
- },
171
- ],
172
- };
173
-
174
- const rule = createTestRule({ conditions });
175
- const context = {
176
- data: { amount: 50, status: "premium" },
177
- timestamp: new Date(),
178
- };
179
-
180
- const evaluator = createEvaluator();
181
- const result = evaluateRule(rule, context, evaluator);
182
-
183
- expect(result.matched).toBe(true);
184
- });
185
- });
186
-
187
- describe("evaluateRules", () => {
188
- test("should evaluate multiple rules", () => {
189
- const rules = [
190
- createTestRule({ id: "rule-1", priority: 10 }),
191
- createTestRule({ id: "rule-2", priority: 5 }),
192
- ];
193
- const context = {
194
- data: { amount: 150 },
195
- timestamp: new Date(),
196
- };
197
-
198
- const evaluator = createEvaluator();
199
- const result = evaluateRules(rules, context, evaluator);
200
-
201
- expect(result.results).toHaveLength(2);
202
- expect(result.matchedRules).toHaveLength(2);
203
- expect(result.stoppedEarly).toBe(false);
204
- });
205
-
206
- test("should stop on stopOnMatch", () => {
207
- const rules = [
208
- createTestRule({ id: "rule-1", stopOnMatch: true }),
209
- createTestRule({ id: "rule-2" }),
210
- ];
211
- const context = {
212
- data: { amount: 150 },
213
- timestamp: new Date(),
214
- };
215
-
216
- const evaluator = createEvaluator();
217
- const result = evaluateRules(rules, context, evaluator);
218
-
219
- expect(result.results).toHaveLength(1);
220
- expect(result.matchedRules).toHaveLength(1);
221
- expect(result.stoppedEarly).toBe(true);
222
- expect(result.stoppedByRuleId).toBe("rule-1");
223
- });
224
-
225
- test("should stop on first-match conflict resolution", () => {
226
- const rules = [
227
- createTestRule({ id: "rule-1" }),
228
- createTestRule({ id: "rule-2" }),
229
- ];
230
- const context = {
231
- data: { amount: 150 },
232
- timestamp: new Date(),
233
- };
234
-
235
- const evaluator = createEvaluator();
236
- const result = evaluateRules(rules, context, evaluator, {
237
- config: { conflictResolution: "first-match" },
238
- });
239
-
240
- expect(result.matchedRules).toHaveLength(1);
241
- expect(result.stoppedEarly).toBe(true);
242
- });
243
-
244
- test("should collect all consequences with priority strategy", () => {
245
- const rules = [
246
- createTestRule({
247
- id: "rule-1",
248
- consequences: [{ type: "action1", payload: {} }],
249
- }),
250
- createTestRule({
251
- id: "rule-2",
252
- consequences: [{ type: "action2", payload: {} }],
253
- }),
254
- ];
255
- const context = {
256
- data: { amount: 150 },
257
- timestamp: new Date(),
258
- };
259
-
260
- const evaluator = createEvaluator();
261
- const result = evaluateRules(rules, context, evaluator);
262
-
263
- expect(result.consequences).toHaveLength(2);
264
- });
265
-
266
- test("should skip disabled rules", () => {
267
- const rules = [
268
- createTestRule({ id: "rule-1", enabled: true }),
269
- createTestRule({ id: "rule-2", enabled: false }),
270
- ];
271
- const context = {
272
- data: { amount: 150 },
273
- timestamp: new Date(),
274
- };
275
-
276
- const evaluator = createEvaluator();
277
- const result = evaluateRules(rules, context, evaluator);
278
-
279
- expect(result.matchedRules).toHaveLength(1);
280
- expect(result.results[1]?.skipped).toBe(true);
281
- });
282
-
283
- test("should call onRuleEvaluated callback", () => {
284
- const rules = [createTestRule({ id: "rule-1" })];
285
- const context = {
286
- data: { amount: 150 },
287
- timestamp: new Date(),
288
- };
289
-
290
- const evaluatedRules: string[] = [];
291
-
292
- const evaluator = createEvaluator();
293
- evaluateRules(rules, context, evaluator, {
294
- onRuleEvaluated: (result) => {
295
- evaluatedRules.push(result.ruleId);
296
- },
297
- });
298
-
299
- expect(evaluatedRules).toContain("rule-1");
300
- });
301
- });
302
-
303
- describe("evaluateRule with custom evaluator", () => {
304
- test("should use provided evaluator for custom operators", () => {
305
- const customOp = createOperator({
306
- name: "always_true",
307
- type: "custom",
308
- evaluate: () => true,
309
- });
310
-
311
- const evaluator = createEvaluator({
312
- operators: { always_true: customOp }
313
- });
314
-
315
- const rule = createTestRule({
316
- conditions: {
317
- id: "group-1",
318
- operator: "AND",
319
- conditions: [
320
- {
321
- id: "cond-1",
322
- type: "custom",
323
- field: "anything",
324
- operator: "always_true",
325
- },
326
- ],
327
- },
328
- });
329
-
330
- const context = {
331
- data: { anything: "value" },
332
- timestamp: new Date(),
333
- };
334
-
335
- const result = evaluateRule(rule, context, evaluator);
336
-
337
- expect(result.matched).toBe(true);
338
- });
339
- });
@@ -1,30 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { createEngine, createEvaluator, createOperator } from "../src/index";
3
-
4
- describe("Public API exports", () => {
5
- test("should export createEvaluator", () => {
6
- expect(createEvaluator).toBeDefined();
7
- expect(typeof createEvaluator).toBe("function");
8
- });
9
-
10
- test("should export createOperator", () => {
11
- expect(createOperator).toBeDefined();
12
- expect(typeof createOperator).toBe("function");
13
- });
14
-
15
- test("should be able to use all exports together", () => {
16
- const customOp = createOperator({
17
- name: "test_op",
18
- type: "custom",
19
- evaluate: () => true,
20
- });
21
-
22
- const evaluator = createEvaluator({ operators: { test_op: customOp } });
23
-
24
- const engine = createEngine({
25
- evaluator,
26
- });
27
-
28
- expect(engine).toBeDefined();
29
- });
30
- });
@@ -1,303 +0,0 @@
1
- import { describe, expect, test } from "bun:test";
2
- import {
3
- filterByCategory,
4
- filterByEnabled,
5
- filterByTags,
6
- filterRules,
7
- } from "../src/core/filter";
8
- import { groupByCategory, groupByCustom, groupRules } from "../src/core/group";
9
- import { sortByName, sortByPriority, sortRules } from "../src/core/sort";
10
- import type { DefaultConsequences } from "../src/types/consequence";
11
- import type { Rule } from "../src/types/rule";
12
-
13
- type TestRule = Rule<unknown, DefaultConsequences>;
14
-
15
- const createTestRule = (overrides: Partial<TestRule> = {}): TestRule => ({
16
- id: "rule-1",
17
- name: "Test Rule",
18
- description: "A test rule",
19
- conditions: {
20
- id: "group-1",
21
- operator: "AND",
22
- conditions: [],
23
- },
24
- consequences: [],
25
- priority: 10,
26
- enabled: true,
27
- stopOnMatch: false,
28
- tags: ["test"],
29
- category: "finance",
30
- createdAt: new Date("2024-01-01"),
31
- updatedAt: new Date("2024-01-01"),
32
- ...overrides,
33
- });
34
-
35
- describe("filterRules", () => {
36
- test("should filter by enabled", () => {
37
- const rules = [
38
- createTestRule({ id: "rule-1", enabled: true }),
39
- createTestRule({ id: "rule-2", enabled: false }),
40
- ];
41
-
42
- const result = filterRules({ enabled: true })(rules);
43
-
44
- expect(result).toHaveLength(1);
45
- expect(result[0]?.id).toBe("rule-1");
46
- });
47
-
48
- test("should filter by tags", () => {
49
- const rules = [
50
- createTestRule({ id: "rule-1", tags: ["finance", "alerts"] }),
51
- createTestRule({ id: "rule-2", tags: ["marketing"] }),
52
- ];
53
-
54
- const result = filterRules({ tags: ["finance"] })(rules);
55
-
56
- expect(result).toHaveLength(1);
57
- expect(result[0]?.id).toBe("rule-1");
58
- });
59
-
60
- test("should filter by category", () => {
61
- const rules = [
62
- createTestRule({ id: "rule-1", category: "finance" }),
63
- createTestRule({ id: "rule-2", category: "marketing" }),
64
- ];
65
-
66
- const result = filterRules({ category: "finance" })(rules);
67
-
68
- expect(result).toHaveLength(1);
69
- expect(result[0]?.id).toBe("rule-1");
70
- });
71
-
72
- test("should filter by ids", () => {
73
- const rules = [
74
- createTestRule({ id: "rule-1" }),
75
- createTestRule({ id: "rule-2" }),
76
- createTestRule({ id: "rule-3" }),
77
- ];
78
-
79
- const result = filterRules({ ids: ["rule-1", "rule-3"] })(rules);
80
-
81
- expect(result).toHaveLength(2);
82
- });
83
-
84
- test("should combine multiple filters", () => {
85
- const rules = [
86
- createTestRule({ id: "rule-1", enabled: true, category: "finance" }),
87
- createTestRule({ id: "rule-2", enabled: false, category: "finance" }),
88
- createTestRule({ id: "rule-3", enabled: true, category: "marketing" }),
89
- ];
90
-
91
- const result = filterRules({ enabled: true, category: "finance" })(rules);
92
-
93
- expect(result).toHaveLength(1);
94
- expect(result[0]?.id).toBe("rule-1");
95
- });
96
- });
97
-
98
- describe("filterByEnabled", () => {
99
- test("should filter enabled rules", () => {
100
- const rules = [
101
- createTestRule({ id: "rule-1", enabled: true }),
102
- createTestRule({ id: "rule-2", enabled: false }),
103
- ];
104
-
105
- const result = filterByEnabled(true)(rules);
106
-
107
- expect(result).toHaveLength(1);
108
- expect(result[0]?.id).toBe("rule-1");
109
- });
110
- });
111
-
112
- describe("filterByTags", () => {
113
- test("should filter by tags", () => {
114
- const rules = [
115
- createTestRule({ id: "rule-1", tags: ["finance"] }),
116
- createTestRule({ id: "rule-2", tags: ["marketing"] }),
117
- ];
118
-
119
- const result = filterByTags(["finance"])(rules);
120
-
121
- expect(result).toHaveLength(1);
122
- });
123
- });
124
-
125
- describe("filterByCategory", () => {
126
- test("should filter by category", () => {
127
- const rules = [
128
- createTestRule({ id: "rule-1", category: "finance" }),
129
- createTestRule({ id: "rule-2", category: "marketing" }),
130
- ];
131
-
132
- const result = filterByCategory("finance")(rules);
133
-
134
- expect(result).toHaveLength(1);
135
- });
136
- });
137
-
138
- describe("sortRules", () => {
139
- test("should sort by priority descending", () => {
140
- const rules = [
141
- createTestRule({ id: "rule-1", priority: 5 }),
142
- createTestRule({ id: "rule-2", priority: 10 }),
143
- createTestRule({ id: "rule-3", priority: 1 }),
144
- ];
145
-
146
- const result = sortRules("priority")(rules);
147
-
148
- expect(result[0]?.id).toBe("rule-2");
149
- expect(result[1]?.id).toBe("rule-1");
150
- expect(result[2]?.id).toBe("rule-3");
151
- });
152
-
153
- test("should sort by priority ascending", () => {
154
- const rules = [
155
- createTestRule({ id: "rule-1", priority: 5 }),
156
- createTestRule({ id: "rule-2", priority: 10 }),
157
- createTestRule({ id: "rule-3", priority: 1 }),
158
- ];
159
-
160
- const result = sortRules({ field: "priority", direction: "asc" })(rules);
161
-
162
- expect(result[0]?.id).toBe("rule-3");
163
- expect(result[1]?.id).toBe("rule-1");
164
- expect(result[2]?.id).toBe("rule-2");
165
- });
166
-
167
- test("should sort by name", () => {
168
- const rules = [
169
- createTestRule({ id: "rule-1", name: "Zebra" }),
170
- createTestRule({ id: "rule-2", name: "Alpha" }),
171
- createTestRule({ id: "rule-3", name: "Beta" }),
172
- ];
173
-
174
- const result = sortRules({ field: "name", direction: "asc" })(rules);
175
-
176
- expect(result[0]?.id).toBe("rule-2");
177
- expect(result[1]?.id).toBe("rule-3");
178
- expect(result[2]?.id).toBe("rule-1");
179
- });
180
-
181
- test("should sort by createdAt", () => {
182
- const rules = [
183
- createTestRule({ id: "rule-1", createdAt: new Date("2024-01-15") }),
184
- createTestRule({ id: "rule-2", createdAt: new Date("2024-01-01") }),
185
- createTestRule({ id: "rule-3", createdAt: new Date("2024-01-10") }),
186
- ];
187
-
188
- const result = sortRules({ field: "createdAt", direction: "desc" })(
189
- rules,
190
- );
191
-
192
- expect(result[0]?.id).toBe("rule-1");
193
- expect(result[2]?.id).toBe("rule-2");
194
- });
195
- });
196
-
197
- describe("sortByPriority", () => {
198
- test("should sort by priority", () => {
199
- const rules = [
200
- createTestRule({ id: "rule-1", priority: 1 }),
201
- createTestRule({ id: "rule-2", priority: 10 }),
202
- ];
203
-
204
- const result = sortByPriority()(rules);
205
-
206
- expect(result[0]?.id).toBe("rule-2");
207
- });
208
- });
209
-
210
- describe("sortByName", () => {
211
- test("should sort by name", () => {
212
- const rules = [
213
- createTestRule({ id: "rule-1", name: "Zebra" }),
214
- createTestRule({ id: "rule-2", name: "Alpha" }),
215
- ];
216
-
217
- const result = sortByName()(rules);
218
-
219
- expect(result[0]?.id).toBe("rule-2");
220
- });
221
- });
222
-
223
- describe("groupRules", () => {
224
- test("should group by category", () => {
225
- const rules = [
226
- createTestRule({ id: "rule-1", category: "finance" }),
227
- createTestRule({ id: "rule-2", category: "finance" }),
228
- createTestRule({ id: "rule-3", category: "marketing" }),
229
- ];
230
-
231
- const result = groupRules("category")(rules);
232
-
233
- expect(result.get("finance")).toHaveLength(2);
234
- expect(result.get("marketing")).toHaveLength(1);
235
- });
236
-
237
- test("should group by priority", () => {
238
- const rules = [
239
- createTestRule({ id: "rule-1", priority: 10 }),
240
- createTestRule({ id: "rule-2", priority: 10 }),
241
- createTestRule({ id: "rule-3", priority: 5 }),
242
- ];
243
-
244
- const result = groupRules("priority")(rules);
245
-
246
- expect(result.get(10)).toHaveLength(2);
247
- expect(result.get(5)).toHaveLength(1);
248
- });
249
-
250
- test("should group by enabled", () => {
251
- const rules = [
252
- createTestRule({ id: "rule-1", enabled: true }),
253
- createTestRule({ id: "rule-2", enabled: false }),
254
- createTestRule({ id: "rule-3", enabled: true }),
255
- ];
256
-
257
- const result = groupRules("enabled")(rules);
258
-
259
- expect(result.get(true)).toHaveLength(2);
260
- expect(result.get(false)).toHaveLength(1);
261
- });
262
-
263
- test("should handle uncategorized rules", () => {
264
- const rules = [
265
- createTestRule({ id: "rule-1", category: undefined }),
266
- createTestRule({ id: "rule-2", category: "finance" }),
267
- ];
268
-
269
- const result = groupRules("category")(rules);
270
-
271
- expect(result.get("uncategorized")).toHaveLength(1);
272
- });
273
- });
274
-
275
- describe("groupByCategory", () => {
276
- test("should group by category", () => {
277
- const rules = [
278
- createTestRule({ id: "rule-1", category: "finance" }),
279
- createTestRule({ id: "rule-2", category: "marketing" }),
280
- ];
281
-
282
- const result = groupByCategory()(rules);
283
-
284
- expect(result.size).toBe(2);
285
- });
286
- });
287
-
288
- describe("groupByCustom", () => {
289
- test("should group by custom function", () => {
290
- const rules = [
291
- createTestRule({ id: "rule-1", priority: 5 }),
292
- createTestRule({ id: "rule-2", priority: 15 }),
293
- createTestRule({ id: "rule-3", priority: 3 }),
294
- ];
295
-
296
- const result = groupByCustom<unknown, DefaultConsequences, string>(
297
- (rule) => (rule.priority >= 10 ? "high" : "low"),
298
- )(rules);
299
-
300
- expect(result.get("high")).toHaveLength(1);
301
- expect(result.get("low")).toHaveLength(2);
302
- });
303
- });