decision_agent 0.2.0 → 0.3.0
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.
- checksums.yaml +4 -4
- data/README.md +41 -1
- data/bin/decision_agent +104 -0
- data/lib/decision_agent/dmn/adapter.rb +135 -0
- data/lib/decision_agent/dmn/cache.rb +306 -0
- data/lib/decision_agent/dmn/decision_graph.rb +327 -0
- data/lib/decision_agent/dmn/decision_tree.rb +192 -0
- data/lib/decision_agent/dmn/errors.rb +30 -0
- data/lib/decision_agent/dmn/exporter.rb +217 -0
- data/lib/decision_agent/dmn/feel/evaluator.rb +797 -0
- data/lib/decision_agent/dmn/feel/functions.rb +420 -0
- data/lib/decision_agent/dmn/feel/parser.rb +349 -0
- data/lib/decision_agent/dmn/feel/simple_parser.rb +276 -0
- data/lib/decision_agent/dmn/feel/transformer.rb +372 -0
- data/lib/decision_agent/dmn/feel/types.rb +276 -0
- data/lib/decision_agent/dmn/importer.rb +77 -0
- data/lib/decision_agent/dmn/model.rb +197 -0
- data/lib/decision_agent/dmn/parser.rb +191 -0
- data/lib/decision_agent/dmn/testing.rb +333 -0
- data/lib/decision_agent/dmn/validator.rb +315 -0
- data/lib/decision_agent/dmn/versioning.rb +229 -0
- data/lib/decision_agent/dmn/visualizer.rb +513 -0
- data/lib/decision_agent/dsl/condition_evaluator.rb +3 -0
- data/lib/decision_agent/dsl/schema_validator.rb +2 -1
- data/lib/decision_agent/evaluators/dmn_evaluator.rb +221 -0
- data/lib/decision_agent/version.rb +1 -1
- data/lib/decision_agent/web/dmn_editor.rb +426 -0
- data/lib/decision_agent/web/public/dmn-editor.css +596 -0
- data/lib/decision_agent/web/public/dmn-editor.html +250 -0
- data/lib/decision_agent/web/public/dmn-editor.js +553 -0
- data/lib/decision_agent/web/public/index.html +3 -0
- data/lib/decision_agent/web/public/styles.css +21 -0
- data/lib/decision_agent/web/server.rb +465 -0
- data/spec/ab_testing/ab_testing_agent_spec.rb +174 -0
- data/spec/auth/rbac_adapter_spec.rb +228 -0
- data/spec/dmn/decision_graph_spec.rb +282 -0
- data/spec/dmn/decision_tree_spec.rb +203 -0
- data/spec/dmn/feel/errors_spec.rb +18 -0
- data/spec/dmn/feel/functions_spec.rb +400 -0
- data/spec/dmn/feel/simple_parser_spec.rb +274 -0
- data/spec/dmn/feel/types_spec.rb +176 -0
- data/spec/dmn/feel_parser_spec.rb +489 -0
- data/spec/dmn/hit_policy_spec.rb +202 -0
- data/spec/dmn/integration_spec.rb +226 -0
- data/spec/examples.txt +1846 -1570
- data/spec/fixtures/dmn/complex_decision.dmn +81 -0
- data/spec/fixtures/dmn/invalid_structure.dmn +31 -0
- data/spec/fixtures/dmn/simple_decision.dmn +40 -0
- data/spec/monitoring/metrics_collector_spec.rb +37 -35
- data/spec/monitoring/monitored_agent_spec.rb +14 -11
- data/spec/performance_optimizations_spec.rb +10 -3
- data/spec/thread_safety_spec.rb +10 -2
- data/spec/web_ui_rack_spec.rb +294 -0
- metadata +65 -1
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "decision_agent/dmn/feel/simple_parser"
|
|
3
|
+
|
|
4
|
+
RSpec.describe DecisionAgent::Dmn::Feel::SimpleParser do
|
|
5
|
+
let(:parser) { described_class.new }
|
|
6
|
+
|
|
7
|
+
describe ".can_parse?" do
|
|
8
|
+
it "returns true for simple arithmetic" do
|
|
9
|
+
expect(described_class.can_parse?("age + 5")).to be true
|
|
10
|
+
expect(described_class.can_parse?("price * 1.1")).to be true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it "returns true for logical expressions" do
|
|
14
|
+
expect(described_class.can_parse?("age >= 18 and status = active")).to be true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it "returns false for lists" do
|
|
18
|
+
expect(described_class.can_parse?("[1, 2, 3]")).to be false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
it "returns false for contexts" do
|
|
22
|
+
expect(described_class.can_parse?("{x: 10}")).to be false
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it "returns false for functions" do
|
|
26
|
+
expect(described_class.can_parse?("sum(scores)")).to be false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
it "returns false for quantified expressions" do
|
|
30
|
+
expect(described_class.can_parse?("some x in list satisfies x > 5")).to be false
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "#parse" do
|
|
35
|
+
describe "literals" do
|
|
36
|
+
it "parses integer" do
|
|
37
|
+
result = parser.parse("42")
|
|
38
|
+
expect(result[:type]).to eq(:literal)
|
|
39
|
+
expect(result[:value]).to eq(42)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "parses float" do
|
|
43
|
+
result = parser.parse("3.14")
|
|
44
|
+
expect(result[:type]).to eq(:literal)
|
|
45
|
+
expect(result[:value]).to eq(3.14)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
it "parses negative number" do
|
|
49
|
+
result = parser.parse("-10")
|
|
50
|
+
expect(result[:type]).to eq(:literal)
|
|
51
|
+
expect(result[:value]).to eq(-10)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it "parses string" do
|
|
55
|
+
result = parser.parse('"hello"')
|
|
56
|
+
expect(result[:type]).to eq(:literal)
|
|
57
|
+
expect(result[:value]).to eq("hello")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "parses boolean true" do
|
|
61
|
+
result = parser.parse("true")
|
|
62
|
+
expect(result[:type]).to eq(:boolean)
|
|
63
|
+
expect(result[:value]).to be true
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "parses boolean false" do
|
|
67
|
+
result = parser.parse("false")
|
|
68
|
+
expect(result[:type]).to eq(:boolean)
|
|
69
|
+
expect(result[:value]).to be false
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
describe "field references" do
|
|
74
|
+
it "parses field name" do
|
|
75
|
+
result = parser.parse("age")
|
|
76
|
+
expect(result[:type]).to eq(:field)
|
|
77
|
+
expect(result[:name]).to eq("age")
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
describe "arithmetic operators" do
|
|
82
|
+
it "parses addition" do
|
|
83
|
+
result = parser.parse("5 + 3")
|
|
84
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
85
|
+
expect(result[:operator]).to eq("+")
|
|
86
|
+
expect(result[:left][:value]).to eq(5)
|
|
87
|
+
expect(result[:right][:value]).to eq(3)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "parses subtraction" do
|
|
91
|
+
result = parser.parse("10 - 3")
|
|
92
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
93
|
+
expect(result[:operator]).to eq("-")
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "parses multiplication" do
|
|
97
|
+
result = parser.parse("4 * 5")
|
|
98
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
99
|
+
expect(result[:operator]).to eq("*")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "parses division" do
|
|
103
|
+
result = parser.parse("20 / 4")
|
|
104
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
105
|
+
expect(result[:operator]).to eq("/")
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
it "parses exponentiation" do
|
|
109
|
+
result = parser.parse("2 ** 3")
|
|
110
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
111
|
+
expect(result[:operator]).to eq("**")
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it "parses modulo" do
|
|
115
|
+
result = parser.parse("10 % 3")
|
|
116
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
117
|
+
expect(result[:operator]).to eq("%")
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe "operator precedence" do
|
|
122
|
+
it "respects multiplication before addition" do
|
|
123
|
+
result = parser.parse("2 + 3 * 4")
|
|
124
|
+
# Should be: 2 + (3 * 4)
|
|
125
|
+
expect(result[:operator]).to eq("+")
|
|
126
|
+
expect(result[:left][:value]).to eq(2)
|
|
127
|
+
expect(result[:right][:operator]).to eq("*")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "respects exponentiation before multiplication" do
|
|
131
|
+
result = parser.parse("2 * 3 ** 2")
|
|
132
|
+
# Should be: 2 * (3 ** 2)
|
|
133
|
+
expect(result[:operator]).to eq("*")
|
|
134
|
+
expect(result[:right][:operator]).to eq("**")
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it "handles parentheses" do
|
|
138
|
+
result = parser.parse("(2 + 3) * 4")
|
|
139
|
+
# Should be: (2 + 3) * 4
|
|
140
|
+
expect(result[:operator]).to eq("*")
|
|
141
|
+
expect(result[:left][:operator]).to eq("+")
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
describe "comparison operators" do
|
|
146
|
+
it "parses greater than or equal" do
|
|
147
|
+
result = parser.parse("age >= 18")
|
|
148
|
+
expect(result[:type]).to eq(:comparison)
|
|
149
|
+
expect(result[:operator]).to eq(">=")
|
|
150
|
+
expect(result[:left][:name]).to eq("age")
|
|
151
|
+
expect(result[:right][:value]).to eq(18)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "parses less than or equal" do
|
|
155
|
+
result = parser.parse("score <= 100")
|
|
156
|
+
expect(result[:type]).to eq(:comparison)
|
|
157
|
+
expect(result[:operator]).to eq("<=")
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it "parses greater than" do
|
|
161
|
+
result = parser.parse("price > 0")
|
|
162
|
+
expect(result[:type]).to eq(:comparison)
|
|
163
|
+
expect(result[:operator]).to eq(">")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
it "parses less than" do
|
|
167
|
+
result = parser.parse("age < 65")
|
|
168
|
+
expect(result[:type]).to eq(:comparison)
|
|
169
|
+
expect(result[:operator]).to eq("<")
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
it "parses not equal" do
|
|
173
|
+
result = parser.parse("status != pending")
|
|
174
|
+
expect(result[:type]).to eq(:comparison)
|
|
175
|
+
expect(result[:operator]).to eq("!=")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
it "parses equal" do
|
|
179
|
+
result = parser.parse("status = active")
|
|
180
|
+
expect(result[:type]).to eq(:comparison)
|
|
181
|
+
expect(result[:operator]).to eq("=")
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
describe "logical operators" do
|
|
186
|
+
it "parses AND" do
|
|
187
|
+
result = parser.parse("age >= 18 and score > 700")
|
|
188
|
+
expect(result[:type]).to eq(:logical)
|
|
189
|
+
expect(result[:operator]).to eq("and")
|
|
190
|
+
expect(result[:left][:type]).to eq(:comparison)
|
|
191
|
+
expect(result[:right][:type]).to eq(:comparison)
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "parses OR" do
|
|
195
|
+
result = parser.parse("status = active or status = pending")
|
|
196
|
+
expect(result[:type]).to eq(:logical)
|
|
197
|
+
expect(result[:operator]).to eq("or")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
it "parses NOT" do
|
|
201
|
+
result = parser.parse("not active")
|
|
202
|
+
expect(result[:type]).to eq(:logical)
|
|
203
|
+
expect(result[:operator]).to eq("not")
|
|
204
|
+
expect(result[:operand][:name]).to eq("active")
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
it "respects AND precedence over OR" do
|
|
208
|
+
result = parser.parse("a or b and c")
|
|
209
|
+
# Should be: a or (b and c)
|
|
210
|
+
expect(result[:operator]).to eq("or")
|
|
211
|
+
expect(result[:right][:operator]).to eq("and")
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
describe "unary operators" do
|
|
216
|
+
it "parses unary minus" do
|
|
217
|
+
result = parser.parse("-5")
|
|
218
|
+
expect(result[:type]).to eq(:literal)
|
|
219
|
+
expect(result[:value]).to eq(-5)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it "parses unary minus with field" do
|
|
223
|
+
result = parser.parse("-age")
|
|
224
|
+
expect(result[:type]).to eq(:arithmetic)
|
|
225
|
+
expect(result[:operator]).to eq("negate")
|
|
226
|
+
expect(result[:operand][:name]).to eq("age")
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
it "parses unary plus (ignored)" do
|
|
230
|
+
result = parser.parse("+5")
|
|
231
|
+
expect(result[:value]).to eq(5)
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
describe "complex expressions" do
|
|
236
|
+
it "parses arithmetic with comparison" do
|
|
237
|
+
result = parser.parse("age + 5 >= 18")
|
|
238
|
+
expect(result[:type]).to eq(:comparison)
|
|
239
|
+
expect(result[:left][:type]).to eq(:arithmetic)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
it "parses multiple logical operations" do
|
|
243
|
+
result = parser.parse("age >= 18 and age <= 65 and status = active")
|
|
244
|
+
expect(result[:type]).to eq(:logical)
|
|
245
|
+
expect(result[:operator]).to eq("and")
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it "parses nested parentheses" do
|
|
249
|
+
result = parser.parse("((age + 5) * 2) >= 40")
|
|
250
|
+
expect(result[:type]).to eq(:comparison)
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
describe "error handling" do
|
|
255
|
+
it "raises error for empty expression" do
|
|
256
|
+
expect do
|
|
257
|
+
parser.parse("")
|
|
258
|
+
end.to raise_error(DecisionAgent::Dmn::FeelParseError, /Empty expression/)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
it "raises error for unbalanced parentheses" do
|
|
262
|
+
expect do
|
|
263
|
+
parser.parse("(age + 5")
|
|
264
|
+
end.to raise_error(DecisionAgent::Dmn::FeelParseError)
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
it "raises error for unexpected character" do
|
|
268
|
+
expect do
|
|
269
|
+
parser.parse("age @ 5")
|
|
270
|
+
end.to raise_error(DecisionAgent::Dmn::FeelParseError, /Unexpected character/)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
end
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
require "decision_agent/dmn/feel/types"
|
|
3
|
+
|
|
4
|
+
RSpec.describe DecisionAgent::Dmn::Feel::Types do
|
|
5
|
+
describe DecisionAgent::Dmn::Feel::Types::Number do
|
|
6
|
+
it "creates from integer" do
|
|
7
|
+
num = DecisionAgent::Dmn::Feel::Types::Number.new(42)
|
|
8
|
+
expect(num.to_ruby).to eq(42)
|
|
9
|
+
expect(num.to_i).to eq(42)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it "creates from float" do
|
|
13
|
+
num = DecisionAgent::Dmn::Feel::Types::Number.new(3.14)
|
|
14
|
+
expect(num.to_ruby).to eq(3.14)
|
|
15
|
+
expect(num.to_f).to be_within(0.001).of(3.14)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "creates from string" do
|
|
19
|
+
num = DecisionAgent::Dmn::Feel::Types::Number.new("42.5")
|
|
20
|
+
expect(num.to_f).to be_within(0.001).of(42.5)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it "supports scale tracking" do
|
|
24
|
+
num = DecisionAgent::Dmn::Feel::Types::Number.new(42, scale: 2)
|
|
25
|
+
expect(num.scale).to eq(2)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "raises error for invalid type" do
|
|
29
|
+
expect do
|
|
30
|
+
DecisionAgent::Dmn::Feel::Types::Number.new([])
|
|
31
|
+
end.to raise_error(DecisionAgent::Dmn::FeelTypeError)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe DecisionAgent::Dmn::Feel::Types::Date do
|
|
36
|
+
it "creates from Time object" do
|
|
37
|
+
time = Time.new(2024, 1, 15)
|
|
38
|
+
date = DecisionAgent::Dmn::Feel::Types::Date.new(time)
|
|
39
|
+
expect(date.to_ruby).to eq(time)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "creates from ISO 8601 string" do
|
|
43
|
+
date = DecisionAgent::Dmn::Feel::Types::Date.new("2024-01-15T10:30:00Z")
|
|
44
|
+
expect(date.to_ruby).to be_a(Time)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "creates from date string" do
|
|
48
|
+
date = DecisionAgent::Dmn::Feel::Types::Date.new("2024-01-15")
|
|
49
|
+
expect(date.to_ruby).to be_a(Time)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "raises error for invalid format" do
|
|
53
|
+
expect do
|
|
54
|
+
DecisionAgent::Dmn::Feel::Types::Date.new("invalid")
|
|
55
|
+
end.to raise_error(DecisionAgent::Dmn::FeelTypeError)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
describe DecisionAgent::Dmn::Feel::Types::Time do
|
|
60
|
+
it "creates from Time object" do
|
|
61
|
+
time = Time.new(2024, 1, 15, 10, 30, 0)
|
|
62
|
+
feel_time = DecisionAgent::Dmn::Feel::Types::Time.new(time)
|
|
63
|
+
expect(feel_time.to_ruby).to eq(time)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
it "creates from ISO 8601 string" do
|
|
67
|
+
feel_time = DecisionAgent::Dmn::Feel::Types::Time.new("2024-01-15T10:30:00Z")
|
|
68
|
+
expect(feel_time.to_ruby).to be_a(Time)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
describe DecisionAgent::Dmn::Feel::Types::Duration do
|
|
73
|
+
it "parses ISO 8601 duration with years" do
|
|
74
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("P1Y")
|
|
75
|
+
expect(duration.years).to eq(1)
|
|
76
|
+
expect(duration.months).to eq(0)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "parses ISO 8601 duration with months" do
|
|
80
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("P3M")
|
|
81
|
+
expect(duration.months).to eq(3)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
it "parses ISO 8601 duration with days" do
|
|
85
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("P10D")
|
|
86
|
+
expect(duration.days).to eq(10)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it "parses ISO 8601 duration with time components" do
|
|
90
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("PT5H30M15S")
|
|
91
|
+
expect(duration.hours).to eq(5)
|
|
92
|
+
expect(duration.minutes).to eq(30)
|
|
93
|
+
expect(duration.seconds).to eq(15)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
it "parses complete ISO 8601 duration" do
|
|
97
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("P1Y2M3DT4H5M6S")
|
|
98
|
+
expect(duration.years).to eq(1)
|
|
99
|
+
expect(duration.months).to eq(2)
|
|
100
|
+
expect(duration.days).to eq(3)
|
|
101
|
+
expect(duration.hours).to eq(4)
|
|
102
|
+
expect(duration.minutes).to eq(5)
|
|
103
|
+
expect(duration.seconds).to eq(6)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
it "converts to seconds" do
|
|
107
|
+
duration = DecisionAgent::Dmn::Feel::Types::Duration.parse("PT1H30M")
|
|
108
|
+
expect(duration.to_seconds).to eq(5400) # 90 minutes
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
it "raises error for invalid format" do
|
|
112
|
+
expect do
|
|
113
|
+
DecisionAgent::Dmn::Feel::Types::Duration.parse("invalid")
|
|
114
|
+
end.to raise_error(DecisionAgent::Dmn::FeelTypeError)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "raises error for non-P prefix" do
|
|
118
|
+
expect do
|
|
119
|
+
DecisionAgent::Dmn::Feel::Types::Duration.parse("1Y2M")
|
|
120
|
+
end.to raise_error(DecisionAgent::Dmn::FeelTypeError, /must start with 'P'/)
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
describe DecisionAgent::Dmn::Feel::Types::List do
|
|
125
|
+
it "wraps array" do
|
|
126
|
+
list = DecisionAgent::Dmn::Feel::Types::List.new([1, 2, 3])
|
|
127
|
+
expect(list.to_ruby).to eq([1, 2, 3])
|
|
128
|
+
expect(list[0]).to eq(1)
|
|
129
|
+
expect(list.length).to eq(3)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
describe DecisionAgent::Dmn::Feel::Types::Context do
|
|
134
|
+
it "wraps hash with symbol keys" do
|
|
135
|
+
ctx = DecisionAgent::Dmn::Feel::Types::Context.new({ "name" => "John", "age" => 30 })
|
|
136
|
+
expect(ctx[:name]).to eq("John")
|
|
137
|
+
expect(ctx[:age]).to eq(30)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
it "converts string keys to symbols" do
|
|
141
|
+
ctx = DecisionAgent::Dmn::Feel::Types::Context.new({ "x" => 10, "y" => 20 })
|
|
142
|
+
expect(ctx.to_ruby).to eq({ x: 10, y: 20 })
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe DecisionAgent::Dmn::Feel::Types::Converter do
|
|
147
|
+
it "converts integer to Number" do
|
|
148
|
+
result = DecisionAgent::Dmn::Feel::Types::Converter.to_feel_type(42)
|
|
149
|
+
expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::Number)
|
|
150
|
+
expect(result.to_ruby).to eq(42)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
it "converts array to List" do
|
|
154
|
+
result = DecisionAgent::Dmn::Feel::Types::Converter.to_feel_type([1, 2, 3])
|
|
155
|
+
expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::List)
|
|
156
|
+
expect(result.to_ruby).to eq([1, 2, 3])
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
it "converts hash to Context" do
|
|
160
|
+
result = DecisionAgent::Dmn::Feel::Types::Converter.to_feel_type({ x: 10 })
|
|
161
|
+
expect(result).to be_a(DecisionAgent::Dmn::Feel::Types::Context)
|
|
162
|
+
expect(result.to_ruby).to eq({ x: 10 })
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "converts FEEL types to Ruby" do
|
|
166
|
+
num = DecisionAgent::Dmn::Feel::Types::Number.new(42)
|
|
167
|
+
result = DecisionAgent::Dmn::Feel::Types::Converter.to_ruby(num)
|
|
168
|
+
expect(result).to eq(42)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "returns non-FEEL types as-is" do
|
|
172
|
+
result = DecisionAgent::Dmn::Feel::Types::Converter.to_ruby("hello")
|
|
173
|
+
expect(result).to eq("hello")
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
end
|