marty 1.2.0 → 1.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/Gemfile.lock +2 -2
- data/app/components/marty/base_rule_view.rb +279 -0
- data/app/components/marty/delorean_rule_view.rb +26 -0
- data/app/components/marty/extras/layout.rb +22 -7
- data/app/components/marty/log_view.rb +1 -1
- data/app/components/marty/mcfly_grid_panel.rb +53 -0
- data/app/components/marty/mcfly_grid_panel/client/dup_in_form.js +20 -0
- data/app/models/marty/base_rule.rb +126 -0
- data/app/models/marty/delorean_rule.rb +121 -0
- data/lib/marty/data_importer.rb +0 -1
- data/lib/marty/rule_script_set.rb +176 -0
- data/lib/marty/version.rb +1 -1
- data/lib/tasks/marty_tasks.rake +42 -0
- data/spec/dummy/app/components/gemini/cm_auth_app.rb +18 -0
- data/spec/dummy/app/components/gemini/my_rule_view.rb +63 -0
- data/spec/dummy/app/components/gemini/xyz_rule_view.rb +25 -0
- data/spec/dummy/app/models/gemini/guard_one.rb +5 -0
- data/spec/dummy/app/models/gemini/guard_two.rb +5 -0
- data/spec/dummy/app/models/gemini/my_rule.rb +46 -0
- data/spec/dummy/app/models/gemini/my_rule_type.rb +5 -0
- data/spec/dummy/app/models/gemini/xyz_enum.rb +5 -0
- data/spec/dummy/app/models/gemini/xyz_rule.rb +63 -0
- data/spec/dummy/app/models/gemini/xyz_rule_type.rb +5 -0
- data/spec/dummy/config/locales/en.yml +10 -0
- data/spec/dummy/db/migrate/20171220150101_add_rule_type_enums.rb +14 -0
- data/spec/dummy/db/migrate/20171221095312_create_gemini_my_rules.rb +22 -0
- data/spec/dummy/db/migrate/20171221095359_create_gemini_xyz_rules.rb +21 -0
- data/spec/dummy/db/migrate/20171222150100_add_rule_indices.rb +34 -0
- data/spec/dummy/db/seeds.rb +1 -1
- data/spec/dummy/delorean/base_code.dl +6 -0
- data/spec/dummy/lib/gemini/my_rule_script_set.rb +13 -0
- data/spec/dummy/lib/gemini/xyz_rule_script_set.rb +22 -0
- data/spec/features/rule_spec.rb +265 -0
- data/spec/fixtures/csv/rule/DataGrid.csv +6 -0
- data/spec/fixtures/csv/rule/MyRule.csv +14 -0
- data/spec/fixtures/csv/rule/XyzRule.csv +6 -0
- data/spec/models/rule_spec.rb +322 -0
- data/spec/support/integration_helpers.rb +1 -0
- metadata +29 -2
@@ -0,0 +1,22 @@
|
|
1
|
+
class Gemini::XyzRuleScriptSet < Marty::RuleScriptSet
|
2
|
+
def self.node_name
|
3
|
+
"NodeXyz"
|
4
|
+
end
|
5
|
+
def self.body_start
|
6
|
+
"import BaseCode\n#{node_name}: BaseCode::BaseCode\n"
|
7
|
+
end
|
8
|
+
def xyz_code(rule)
|
9
|
+
write_code(rule.computed_guards.select{|k,_|k.starts_with?("xyz_")})
|
10
|
+
end
|
11
|
+
def guard_code(rule)
|
12
|
+
write_code(rule.computed_guards.reject{|k,_|k.starts_with?("xyz_")})
|
13
|
+
end
|
14
|
+
def get_code(rule)
|
15
|
+
x = xyz_code(rule)
|
16
|
+
super + (x.blank? ? '' :
|
17
|
+
"XyzNode:\n xyz_param =? nil\n" + self.class.indent(x))
|
18
|
+
end
|
19
|
+
def code_section_counts(rule)
|
20
|
+
super + { xyz: xyz_code(rule).count("\n") }
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,265 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'marty_rspec'
|
3
|
+
|
4
|
+
feature 'rule view', js: true do
|
5
|
+
before(:all) do
|
6
|
+
marty_whodunnit
|
7
|
+
@save_file = "/tmp/save_#{Process.pid}.psql"
|
8
|
+
save_clean_db(@save_file)
|
9
|
+
Marty::Script.load_scripts
|
10
|
+
dt = DateTime.parse('2017-1-1')
|
11
|
+
p = File.expand_path('../../fixtures/csv/rule', __FILE__)
|
12
|
+
[Marty::DataGrid, Gemini::XyzRule, Gemini::MyRule].each do |klass|
|
13
|
+
f = "%s/%s.csv" % [p, klass.to_s.sub(/(Gemini|Marty)::/,'')]
|
14
|
+
Marty::DataImporter.do_import(klass, File.read(f), dt, nil, nil, ",")
|
15
|
+
end
|
16
|
+
end
|
17
|
+
after(:all) do
|
18
|
+
restore_clean_db(@save_file)
|
19
|
+
end
|
20
|
+
def go_to_my_rules
|
21
|
+
press("Pricing Config.")
|
22
|
+
press("My Rules")
|
23
|
+
expect(page).to have_content 'My Rules'
|
24
|
+
end
|
25
|
+
def go_to_xyz_rules
|
26
|
+
press("Pricing Config.")
|
27
|
+
press("Xyz Rules")
|
28
|
+
expect(page).to have_content 'Xyz Rules'
|
29
|
+
end
|
30
|
+
def col_id(v, col)
|
31
|
+
run_js <<-JS
|
32
|
+
#{ext_var(v.grid, 'grid')}
|
33
|
+
return #{ext_find(ext_arg('gridcolumn', text: col), 'grid')}.id
|
34
|
+
JS
|
35
|
+
end
|
36
|
+
# click_checkbox in marty_rspec not working here for some reason
|
37
|
+
def click_checkbox(name)
|
38
|
+
q = %Q(checkbox[fieldLabel="#{name}"])
|
39
|
+
id = run_js "return Ext.ComponentQuery.query('#{q}')[0].inputId"
|
40
|
+
find_by_id(id).click
|
41
|
+
end
|
42
|
+
# click_col in marty_rspec is not reliable
|
43
|
+
def click_column(rv,name)
|
44
|
+
cid = col_id(rv, name)
|
45
|
+
c = find('#'+cid)
|
46
|
+
c.select_option # .click does not work reliably
|
47
|
+
c.send_keys(' ')
|
48
|
+
# wait_for_ajax and wait_for_ready do not work here,
|
49
|
+
# or in the next two methods
|
50
|
+
sleep 1.0
|
51
|
+
end
|
52
|
+
|
53
|
+
def column_filter(rv,name,value)
|
54
|
+
cid = col_id(rv, name)
|
55
|
+
c = find('#'+cid)
|
56
|
+
c.send_keys([:down, :down, :down, :down, :right, value, :return])
|
57
|
+
sleep 1.0
|
58
|
+
end
|
59
|
+
def column_filter_toggle(rv,name)
|
60
|
+
cid = col_id(rv, name)
|
61
|
+
c = find('#'+cid)
|
62
|
+
c.send_keys([:down, :down, :down, :down, ' ', :escape])
|
63
|
+
sleep 1.0
|
64
|
+
end
|
65
|
+
# idx 0 is the start dt, 1 is the end dt
|
66
|
+
def date_fill_in(idx, value)
|
67
|
+
dt = all(:xpath, "//input[contains(@name, 'datefield')]")[idx]
|
68
|
+
dt.native.clear()
|
69
|
+
dt.native.send_keys(value)
|
70
|
+
end
|
71
|
+
def time_fill_in(idx,value)
|
72
|
+
tm = all(:xpath, "//input[contains(@name, 'timefield')]")[idx]
|
73
|
+
tm.native.clear()
|
74
|
+
tm.native.send_keys(value)
|
75
|
+
end
|
76
|
+
it "rule workflow" do
|
77
|
+
log_in_as('marty')
|
78
|
+
page.driver.browser.manage.window.maximize
|
79
|
+
go_to_my_rules
|
80
|
+
mrv = netzke_find("my_rule_view")
|
81
|
+
# test required field
|
82
|
+
press('Add')
|
83
|
+
wait_for_ajax
|
84
|
+
fill_in('name', with: 'abc')
|
85
|
+
press('OK')
|
86
|
+
expect(page).to have_content("Rule type can't be blank")
|
87
|
+
expect(page).to have_content("Start dt can't be blank")
|
88
|
+
# create and verify rule
|
89
|
+
fill_in('rule_type', with: 'SimpleRule')
|
90
|
+
date_fill_in(0, '2013-01-01')
|
91
|
+
time_fill_in(0, '11:03:01')
|
92
|
+
date_fill_in(1, '2030-01-01')
|
93
|
+
time_fill_in(1, '08:03:01')
|
94
|
+
press("OK")
|
95
|
+
wait_for_ajax
|
96
|
+
expect(mrv.row_count()).to eq(8)
|
97
|
+
expect(mrv.get_row_vals(1)).to include({"name"=>"abc",
|
98
|
+
"rule_type"=>"SimpleRule",
|
99
|
+
"start_dt"=>"2013-01-01T19:03:01.000Z",
|
100
|
+
"end_dt"=>"2030-01-01T16:03:01.000Z",
|
101
|
+
"other_flag"=>false,
|
102
|
+
"g_array"=>"",
|
103
|
+
"g_single"=>"",
|
104
|
+
"g_string"=>"",
|
105
|
+
"g_bool"=>nil,
|
106
|
+
"g_range"=>nil,
|
107
|
+
"g_integer"=>nil,
|
108
|
+
"computed_guards"=>"",
|
109
|
+
"grids"=>"",
|
110
|
+
"results"=>"",
|
111
|
+
})
|
112
|
+
r = Gemini::MyRule.lookup('infinity','abc')
|
113
|
+
expect(r.as_json).to include({"user_id"=>1,
|
114
|
+
"o_user_id"=>nil,
|
115
|
+
"name"=>"abc",
|
116
|
+
"engine"=>"Gemini::MyRuleScriptSet",
|
117
|
+
"rule_type"=>"SimpleRule",
|
118
|
+
"simple_guards"=>{"g_has_default"=>
|
119
|
+
"string default"},
|
120
|
+
"computed_guards"=>{},
|
121
|
+
"grids"=>{},
|
122
|
+
"results"=>{},
|
123
|
+
})
|
124
|
+
# type validation (string with values list)
|
125
|
+
mrv.select_row(1)
|
126
|
+
press("Edit")
|
127
|
+
fill_in(:g_string, with: "12345")
|
128
|
+
press("OK")
|
129
|
+
expect(page).to have_content("Bad value '12345' for 'g_string'")
|
130
|
+
# type validation (range)
|
131
|
+
fill_in(:g_string, with: "Hi Mom")
|
132
|
+
click_checkbox("Bool Guard")
|
133
|
+
click_checkbox("Other")
|
134
|
+
netzke_find('Array Guard', 'combobox').select_values("G1V1,G1V3")
|
135
|
+
netzke_find('Single Guard', 'combobox').select_values("G2V2")
|
136
|
+
fill_in(:g_integer, with: 123)
|
137
|
+
fill_in(:g_range, with: "asfd")
|
138
|
+
press("OK")
|
139
|
+
expect(page).to have_content("Wrong type for 'g_range'")
|
140
|
+
# validate rule
|
141
|
+
fill_in(:g_range, with: "<=100")
|
142
|
+
netzke_find('Grid1', 'combobox').select_values("DataGrid1")
|
143
|
+
netzke_find('Grid2', 'combobox').select_values("DataGrid2")
|
144
|
+
press("OK")
|
145
|
+
wait_for_ajax
|
146
|
+
exp = {"name"=>"abc",
|
147
|
+
"rule_type"=>"SimpleRule",
|
148
|
+
"start_dt"=>"2013-01-01T19:03:01.000Z",
|
149
|
+
"end_dt"=>"2030-01-01T16:03:01.000Z",
|
150
|
+
"other_flag"=>true,
|
151
|
+
"g_array"=>"G1V1,G1V3",
|
152
|
+
"g_single"=>"G2V2",
|
153
|
+
"g_string"=>"Hi Mom",
|
154
|
+
"g_bool"=>true,
|
155
|
+
"g_range"=>"<=100",
|
156
|
+
"g_integer"=>123,
|
157
|
+
"computed_guards"=>"",
|
158
|
+
"grids"=>"{\"grid1\":\"DataGrid1\",\"grid2\":\"DataGrid2\"}",
|
159
|
+
"results"=>"",
|
160
|
+
}
|
161
|
+
expect(mrv.get_row_vals(1)).to include(exp)
|
162
|
+
# grid edits
|
163
|
+
press("Edit")
|
164
|
+
netzke_find('Grid2', 'combobox').select_values("---")
|
165
|
+
press("OK")
|
166
|
+
wait_for_ajax
|
167
|
+
expect(mrv.get_row_vals(1)).to include(exp+{"grids"=>
|
168
|
+
"{\"grid1\":\"DataGrid1\"}"})
|
169
|
+
# computed fields
|
170
|
+
press("Edit")
|
171
|
+
fill_in(:computed_guards, with: 'sadf asdf ljsf')
|
172
|
+
press("OK")
|
173
|
+
exp = "Computed - Error in rule 'abc' field 'computed_guards': Syntax error on line 1"
|
174
|
+
expect(page).to have_content(exp)
|
175
|
+
fill_in(:computed_guards, with: 'sadf = 123j s /*fdjOIb')
|
176
|
+
press("OK")
|
177
|
+
exp = "Computed - Error in rule 'abc' field 'computed_guards': syntax error"
|
178
|
+
expect(page).to have_content(exp)
|
179
|
+
fill_in(:computed_guards, with: '')
|
180
|
+
fill_in(:results, with: %Q(abc = "def"\ndef = 5\nxyz=def+10\nsadf asdf lsf))
|
181
|
+
press("OK")
|
182
|
+
exp = "Computed - Error in rule 'abc' field 'results': Syntax error on line 4"
|
183
|
+
expect(page).to have_content(exp)
|
184
|
+
fill_in(:results,
|
185
|
+
with: %Q(abc = "def"\ndef = "abc"\nklm = "3"\nabc = "xyz"))
|
186
|
+
exp = "Computed - Error in rule 'abc' field 'results': Keyword 'abc' specified more"\
|
187
|
+
" than once (line 4)"
|
188
|
+
press("OK")
|
189
|
+
expect(page).to have_content(exp)
|
190
|
+
fill_in(:results,
|
191
|
+
with: %Q(abc = "def"\ndef = "abc"\nklm = "3"))
|
192
|
+
press("OK")
|
193
|
+
|
194
|
+
press("Dup in form")
|
195
|
+
press("OK")
|
196
|
+
exp = Regexp.new("Can't have rule with same name and overlapping start"\
|
197
|
+
"/end dates - abc")
|
198
|
+
expect(page).to have_content(exp)
|
199
|
+
netzke_find('Single Guard', 'combobox').select_values("G2V3")
|
200
|
+
press("OK")
|
201
|
+
exp = Regexp.new("Can't have rule with same name and different "\
|
202
|
+
"type/guards - abc")
|
203
|
+
expect(page).to have_content(exp)
|
204
|
+
|
205
|
+
press("Cancel")
|
206
|
+
# column sorting, etc
|
207
|
+
go_to_xyz_rules
|
208
|
+
xrv = netzke_find("xyz_rule_view")
|
209
|
+
expect(page).to have_content("Rule type")
|
210
|
+
expect(xrv.col_values(:name, 5, 0)).to eq(["ZRule1", "ZRule2",
|
211
|
+
"ZRule3", "ZRule4",
|
212
|
+
"ZRule5"])
|
213
|
+
xrv.select_row(1)
|
214
|
+
press("Edit")
|
215
|
+
fill_in("Range Guard 1", with: "[100,200)")
|
216
|
+
fill_in("Range Guard 2", with: "[30,40)")
|
217
|
+
press("OK")
|
218
|
+
r = Gemini::XyzRule.get_matches('infinity', {}, {"g_range1"=> 150,
|
219
|
+
"g_range2"=> 35})
|
220
|
+
expect(r.to_a.count).to eq(1)
|
221
|
+
exp = {"user_id"=>1,
|
222
|
+
"o_user_id"=>nil,
|
223
|
+
"name"=>"ZRule1",
|
224
|
+
"engine"=>"Gemini::XyzRuleScriptSet",
|
225
|
+
"rule_type"=>"ZRule",
|
226
|
+
"start_dt"=>DateTime.parse("2017-1-1 08:01:00"),
|
227
|
+
"simple_guards"=>{"g_date"=>"2017-1-1",
|
228
|
+
"g_range1"=>"[100,200)",
|
229
|
+
"g_range2"=>"[30,40)",
|
230
|
+
"g_string"=>"aaa",
|
231
|
+
"g_integer"=>"5",
|
232
|
+
"g_datetime"=>"2017-1-1 12:00:01"},
|
233
|
+
"computed_guards"=>{},
|
234
|
+
"grids"=>{"grid1"=>"DataGrid1"},
|
235
|
+
"results"=>
|
236
|
+
{"bvlen"=>"base_value.length",
|
237
|
+
"bv"=>"base_value"}}
|
238
|
+
|
239
|
+
expect(r.first.as_json).to include(exp)
|
240
|
+
expect(xrv.col_values(:g_string, 5, 0)).to eq(["aaa", "bbb", "ccc",
|
241
|
+
"ddd", "eee"])
|
242
|
+
click_column(xrv, "G string")
|
243
|
+
expect(xrv.col_values(:g_string, 5, 0)).to eq(["eee", "ddd", "ccc",
|
244
|
+
"bbb", "aaa"])
|
245
|
+
column_filter(xrv, "G string", "eee")
|
246
|
+
rc = xrv.row_count
|
247
|
+
expect(xrv.col_values(:g_string,rc,0)).to eq(["eee"])
|
248
|
+
column_filter_toggle(xrv, "G string")
|
249
|
+
rc = xrv.row_count
|
250
|
+
expect(xrv.col_values(:g_string,rc,0)).to eq(["eee", "ddd", "ccc",
|
251
|
+
"bbb", "aaa"])
|
252
|
+
column_filter(xrv, "Grids", "Grid1")
|
253
|
+
rc = xrv.row_count
|
254
|
+
# netzke reports jsonb as string
|
255
|
+
expect(xrv.col_values(:grids,rc,0)).to eq([%Q({"grid1":"DataGrid1"}),
|
256
|
+
%Q({"grid1":"DataGrid1"})])
|
257
|
+
column_filter_toggle(xrv, "Grids")
|
258
|
+
rc = xrv.row_count
|
259
|
+
expect(xrv.col_values(:grids,rc,0)).to eq([%Q({"grid1":"DataGrid3"}),
|
260
|
+
%Q({"grid1":"DataGrid3"}),
|
261
|
+
%Q({"grid1":"DataGrid2"}),
|
262
|
+
%Q({"grid1":"DataGrid1"}),
|
263
|
+
%Q({"grid1":"DataGrid1"})])
|
264
|
+
end
|
265
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
name,data,metadata,data_type,lenient
|
2
|
+
DataGrid1,"[[9,8,7,6,5,4,3,2,1,0]]","[{""dir"": ""h"", ""attr"": ""param1"", ""type"": ""int4range"", ""keys"": [""[,10]"",""(10,20]"",""(20,30]"",""(30,40]"",""(40,50]"",""(50,60]"",""(60,70]"",""(70,80]"",""(80,90]"",""(90,100]""]}]",,t
|
3
|
+
DataGrid2,"[[9,8,7,6,5,4,3,2,1,0],[19,18,17,16,15,14,1300,12,11,10]]","[{""dir"": ""h"", ""attr"": ""param1"", ""type"": ""int4range"", ""keys"": [""[,10]"",""(10,20]"",""(20,30]"",""(30,40]"",""(40,50]"",""(50,60]"",""(60,70]"",""(70,80]"",""(80,90]"",""(90,100]""]},{""dir"":""v"",""attr"":""paramb"",""type"":""boolean"",""keys"":[true, false]}]",,t
|
4
|
+
DataGrid3,"[[9,8,7,6,5,4,3,2,1,0],[19,18,17,16,15,14,1300,12,11,10]]","[{""dir"": ""h"", ""attr"": ""p2"", ""type"": ""int4range"", ""keys"": [""[,10]"",""(10,20]"",""(20,30]"",""(30,40]"",""(40,50]"",""(50,60]"",""(60,70]"",""(70,80]"",""(80,90]"",""(90,100]""]},{""dir"":""v"",""attr"":""flavor"",""type"":""string"",""keys"":[[""lemon""], [""cherry""]]}]",,f
|
5
|
+
DataGrid4,"[[9,8,7,6,5,4,3,2,1,0],[19,18,17,16,15,14,1300,12,11,10]]","[{""dir"": ""h"", ""attr"": ""param1"", ""type"": ""int4range"", ""keys"": [""[,10]"",""(10,20]"",""(20,30]"",""(30,40]"",""(40,50]"",""(50,60]"",""(60,70]"",""(70,80]"",""(80,90]"",""(90,100]""]},{""dir"":""v"",""attr"":""flavor"",""type"":""string"",""keys"":[[""lemon""], [""cherry""]]}]",,f
|
6
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
name,rule_type,start_dt,end_dt,other_flag,simple_guards,computed_guards,grids,results
|
2
|
+
Rule1,SimpleRule,2017-1-1 12:00:00,2017-4-1,,"{""g_has_default"":""different"",""g_array"":[""G1V1"",""G1V3""],""g_single"":""G2V2"",""g_string"":""Hi Mom"",""g_bool"":true,""g_range"":""[50,)"",""g_integer"":10}",,,"{""simple_result"":""\""a value\""""}"
|
3
|
+
Rule2,SimpleRule,2017-2-1 14:00:00,2017-4-1,true,"{""g_array"":[""G1V2""],""g_single"":""G2V3"",""g_string"":""abc"",""g_bool"":true,""g_range"":""(,50]"",""g_integer"":11}",,,"{
|
4
|
+
""simple_result"":""\""b value\"" # with comment "",
|
5
|
+
""sr2"":""true # with comment"",
|
6
|
+
""sr3"": ""123 # with comment "",
|
7
|
+
""c1"":""123 + 456 # with comment"",
|
8
|
+
""single_quote"":""'string with single quotes'"",
|
9
|
+
""stringwithhash"": ""\"" string that contains a # character\""""}"
|
10
|
+
Rule2a,SimpleRule,2017-2-1 14:00:00,2017-4-1,true,"{""g_array"":[""G1V2""],""g_single"":""G2V3"",""g_string"":""abc"",""g_bool"":true,""g_range"":""(,50]"",""g_integer"":99}",,,"{""simple_result"":""\""b value\"""", ""sr2"":""true"", ""sr3"": ""123""}"
|
11
|
+
Rule2b,SimpleRule,2017-2-1 14:00:00,2017-4-1,true,"{""g_array"":[""G1V2""],""g_single"":""G2V3"",""g_string"":""abc"",""g_bool"":true,""g_range"":""(,50]"",""g_integer"":999}",,"{""grid1"":""DataGrid1"",""grid2"":""DataGrid2""}",
|
12
|
+
Rule3,ComplexRule,2017-3-1 00:00:00,2017-4-1,false,"{""g_array"":[""G1V2"",""G1V3""],""g_string"":""def"",""g_integer"":11}","{""cguard1"":""1==1"",""cguard2"":""param2 == 'abc'""}","{""grid1"":""DataGrid1"",""grid2"":""DataGrid2""}","{""simple_result"":""\""c value\"""",""computed_value"":""if paramb then param1 / (grid1_grid||1) else (grid2_grid||1) / param1""}"
|
13
|
+
Rule4,ComplexRule,2017-4-1 15:00:01,2017-5-1,,"{""g_array"":[""G1V2"",""G1V3""],""g_string"":""Hi Mom"",""g_integer"":11}","{""cguard1"":""1==1"",""cguard2"":""param2 == \""abc\""""}","{""grid1"":""DataGrid1"",""grid2"":""DataGrid2""}","{""simple_result"":""\""c value\"""",""grid_sum"":""(grid1_grid||0)+(grid2_grid||0)""}"
|
14
|
+
Rule5,ComplexRule,2017-4-2 15:00:01,2017-5-1,,"{""g_string"":""zzz"",""g_integer"":3757,""g_has_default"":""foo""}","{""cguard1"":""1==1""}",,"{""flavor"": ""[\""cherry\"",\""lemon\""][param2]"",""other_grid"": ""DataGrid4"",""final_value"": ""other_grid * 3""}"
|
@@ -0,0 +1,6 @@
|
|
1
|
+
name,rule_type,start_dt,simple_guards,computed_guards,grids,results
|
2
|
+
ZRule1,ZRule,2017-1-1 08:01:00,"{""g_string"":""aaa"",""g_range1"":""[10,20)"",""g_integer"":5,""g_date"":""2017-1-1"",""g_datetime"":""2017-1-1 12:00:01""}","{}","{""grid1"":""DataGrid1""}","{""bvlen"": ""base_value.length"", ""bv"":""base_value""}"
|
3
|
+
ZRule2,ZRule,2017-2-1 09:01:00,"{""g_string"":""bbb"",""g_range1"":""[20,30)"",""g_integer"":4,""g_date"":""2017-1-2"",""g_datetime"":""2017-1-1 12:00:02""}","{}","{""grid1"":""DataGrid1""}","{""key1"":""\""value1\"""",""bvlen"": ""base_value.length"", ""bv"":""base_value""}"
|
4
|
+
ZRule3,ZRule,2017-3-1 10:00:00,"{""g_string"":""ccc"",""g_range1"":""[30,40)"",""g_integer"":3,""g_date"":""2017-1-3"",""g_datetime"":""2017-1-1 12:00:03""}","{}","{""grid1"":""DataGrid2""}","{""bvlen"": ""base_value.length"", ""bv"":""base_value""}"
|
5
|
+
ZRule4,ZRule,2017-3-1 11:00:00,"{""g_string"":""ddd"",""g_range1"":""[40,50)"",""g_integer"":2,""g_date"":""2017-1-4"",""g_datetime"":""2017-1-1 12:00:04""}","{}","{""grid1"":""DataGrid3""}","{""bvlength"": ""base_value.length"", ""bv"":""base_value""}"
|
6
|
+
ZRule5,ZRule,2017-3-1 11:00:01,"{""g_string"":""eee"",""g_range1"":""[50,60)"",""g_integer"":1,""g_date"":""2017-1-5"",""g_datetime"":""2017-1-1 12:00:05""}","{}","{""grid1"":""DataGrid3""}","{""bvlen"": ""base_value.length"", ""bv"":""base_value""}"
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Marty::RuleSpec
|
4
|
+
describe "Rule" do
|
5
|
+
before(:all) do
|
6
|
+
@save_file = "/tmp/save_#{Process.pid}.psql"
|
7
|
+
save_clean_db(@save_file)
|
8
|
+
Marty::Script.load_scripts
|
9
|
+
Marty::Config['RULEOPTS_MYRULE']={'simple_result'=>{},
|
10
|
+
'computed_value'=>{},
|
11
|
+
'final_value'=>{},
|
12
|
+
}
|
13
|
+
Marty::Config['RULEOPTS_XYZ']={'bvlength'=>{},
|
14
|
+
'bv'=>{},
|
15
|
+
}
|
16
|
+
end
|
17
|
+
after(:all) do
|
18
|
+
restore_clean_db(@save_file)
|
19
|
+
end
|
20
|
+
before(:each) do
|
21
|
+
marty_whodunnit
|
22
|
+
dt = DateTime.parse('2017-1-1')
|
23
|
+
p = File.expand_path('../../fixtures/csv/rule', __FILE__)
|
24
|
+
[Marty::DataGrid, Gemini::XyzRule, Gemini::MyRule].each do |klass|
|
25
|
+
f = "%s/%s.csv" % [p, klass.to_s.sub(/(Gemini|Marty)::/,'')]
|
26
|
+
Marty::DataImporter.do_import(klass, File.read(f), dt, nil, nil, ",")
|
27
|
+
end
|
28
|
+
Marty::Tag.do_create('2017-01-01', 'tag')
|
29
|
+
end
|
30
|
+
context "validation" do
|
31
|
+
subject do
|
32
|
+
guards = (@g_array ? {"g_array" =>@g_array} : {}) +
|
33
|
+
(@g_single ? {"g_single" =>@g_single} : {}) +
|
34
|
+
(@g_string ? {"g_string" =>@g_string} : {}) +
|
35
|
+
(@g_bool ? {"g_bool" =>@g_bool} : {}) +
|
36
|
+
(@g_range ? {"g_range" =>@g_range} : {}) +
|
37
|
+
(@g_integer ? {"g_integer" =>@g_integer} : {})
|
38
|
+
Gemini::MyRule.create!(name: "testrule",
|
39
|
+
rule_type: @rule_type,
|
40
|
+
start_dt: @start_dt || '2013-1-1',
|
41
|
+
end_dt: @end_dt,
|
42
|
+
simple_guards: guards,
|
43
|
+
computed_guards: @computed_guards || {},
|
44
|
+
grids: @grids || {},
|
45
|
+
results: @results || {}
|
46
|
+
)
|
47
|
+
end
|
48
|
+
it "detects type errors" do
|
49
|
+
@rule_type = 'SimpleRule'
|
50
|
+
@g_integer = "abc"
|
51
|
+
expect{subject}.to raise_error(/Guards - Wrong type for 'g_integer'/)
|
52
|
+
end
|
53
|
+
it "detects value errors 1" do
|
54
|
+
@rule_type = "SimpleRule"
|
55
|
+
@g_array = ["G1V1", "abcd"]
|
56
|
+
expect{subject}.to raise_error(/Guards - Bad value 'abcd' for 'g_array'/)
|
57
|
+
end
|
58
|
+
it "detects value errors 2" do
|
59
|
+
@rule_type = "SimpleRule"
|
60
|
+
@g_array = ["G1V1", "xyz", "abc"]
|
61
|
+
exp = /Guards - Bad values 'xyz', 'abc' for 'g_array'/
|
62
|
+
expect{subject}.to raise_error(exp)
|
63
|
+
end
|
64
|
+
it "detects arity errors 1" do
|
65
|
+
@rule_type = "SimpleRule"
|
66
|
+
@g_single = ["G2V1","G2V2"]
|
67
|
+
exp = /Guards - Wrong arity for 'g_single' .expected single got multi./
|
68
|
+
expect{subject}.to raise_error(exp)
|
69
|
+
end
|
70
|
+
it "detects arity errors 2" do
|
71
|
+
@rule_type = "SimpleRule"
|
72
|
+
@g_array = "G1V1"
|
73
|
+
exp = /Guards - Wrong arity for 'g_array' .expected multi got single./
|
74
|
+
expect{subject}.to raise_error(exp)
|
75
|
+
end
|
76
|
+
it "detects errors in computed guards" do
|
77
|
+
@rule_type = "SimpleRule"
|
78
|
+
@computed_guards = {"guard1"=> "zvjsdf12.z8*"}
|
79
|
+
exp = /Computed - Error in rule 'testrule' field 'computed_guards': syntax error/
|
80
|
+
expect{subject}.to raise_error(exp)
|
81
|
+
end
|
82
|
+
it "detects errors in computed results" do
|
83
|
+
@rule_type = "SimpleRule"
|
84
|
+
@results = {"does_not_compute"=> "zvjsdf12.z8*"}
|
85
|
+
@grids = {"grid1"=>"DataGrid1","grid2"=>"DataGrid2"}
|
86
|
+
exp = /Computed - Error in rule 'testrule' field 'results': syntax error/
|
87
|
+
expect{subject}.to raise_error(exp)
|
88
|
+
end
|
89
|
+
it "detects errors in computed results 2" do
|
90
|
+
@rule_type = "SimpleRule"
|
91
|
+
@results = {"does_not_compute"=> "zvjsdf12.z8*"}
|
92
|
+
@grids = {"grid1"=>"DataGrid1","grid2"=>"DataGrid1","grid3"=>"DataGrid3"}
|
93
|
+
exp = /Computed - Error in rule 'testrule' field 'results': syntax error/
|
94
|
+
expect{subject}.to raise_error(exp)
|
95
|
+
end
|
96
|
+
it "detects errors in computed results 3" do
|
97
|
+
@rule_type = "SimpleRule"
|
98
|
+
@results = {"does_not_compute"=> "zvjsdf12.z8*"}
|
99
|
+
@grids = {"grid1"=>"DataGrid1","grid2"=>"DataGrid1","grid3"=>"DataGrid1"}
|
100
|
+
exp = /Computed - Error in rule 'testrule' field 'results': syntax error/
|
101
|
+
expect{subject}.to raise_error(exp)
|
102
|
+
end
|
103
|
+
it "reports bad grid names" do
|
104
|
+
@rule_type = "SimpleRule"
|
105
|
+
@grids = {"grid1"=>"xyz","grid2"=>"DataGrid2","grid3"=>"DataGrid1"}
|
106
|
+
exp = /Grids - Bad grid name 'xyz' for 'grid1'/
|
107
|
+
expect{subject}.to raise_error(exp)
|
108
|
+
end
|
109
|
+
it "sets guard defaults correctly" do
|
110
|
+
vals = Gemini::MyRule.all.map do
|
111
|
+
|r|
|
112
|
+
[r.name, r.simple_guards["g_has_default"]]
|
113
|
+
end
|
114
|
+
expect(vals).to eq([["Rule1", "different"],
|
115
|
+
["Rule2", "string default"],
|
116
|
+
["Rule2a", "string default"],
|
117
|
+
["Rule2b", "string default"],
|
118
|
+
["Rule3", "string default"],
|
119
|
+
["Rule4", "string default"],
|
120
|
+
["Rule5", "foo"]])
|
121
|
+
end
|
122
|
+
end
|
123
|
+
context "validation (xyz type)" do
|
124
|
+
subject do
|
125
|
+
r=Gemini::XyzRule.create!(name: "testrule",
|
126
|
+
rule_type: @rule_type,
|
127
|
+
start_dt: @start_dt || '2013-1-1',
|
128
|
+
end_dt: @end_dt,
|
129
|
+
simple_guards: {},
|
130
|
+
computed_guards: @computed_guards || {},
|
131
|
+
grids: @grids || {},
|
132
|
+
results: @results || {}
|
133
|
+
)
|
134
|
+
r.reload
|
135
|
+
end
|
136
|
+
it "detects script errors" do
|
137
|
+
@rule_type = 'XRule'
|
138
|
+
@results = {"x"=>"zx sdf wer"}
|
139
|
+
exp = /Computed - Error in rule 'testrule' field 'results': syntax error/
|
140
|
+
expect{subject}.to raise_error(exp)
|
141
|
+
end
|
142
|
+
it "rule script stuff overrides 1" do
|
143
|
+
@rule_type = 'XRule'
|
144
|
+
@computed_guards = {"abc"=>"true", "xyz_guard"=> "err err err"}
|
145
|
+
exp = /Computed - Error in rule 'testrule' field 'xyz': syntax error/
|
146
|
+
expect{subject}.to raise_error(exp)
|
147
|
+
end
|
148
|
+
it "rule script stuff overrides 2" do
|
149
|
+
@rule_type = 'XRule'
|
150
|
+
@computed_guards = {"abc"=>"err err err", "xyz_guard"=> "xyz_param"}
|
151
|
+
exp = /Computed - Error in rule 'testrule' field 'computed_guards': syntax error/
|
152
|
+
expect{subject}.to raise_error(exp)
|
153
|
+
end
|
154
|
+
it "rule script stuff overrides 3" do
|
155
|
+
@rule_type = 'XRule'
|
156
|
+
@computed_guards = {"abc"=>"true", "xyz_guard"=> "!xyz_param"}
|
157
|
+
rule = subject
|
158
|
+
expect(rule.compute_xyz('infinity',true)).to be false
|
159
|
+
expect(rule.compute_xyz('infinity',false)).to be true
|
160
|
+
end
|
161
|
+
it "no error" do
|
162
|
+
@rule_type = 'XRule'
|
163
|
+
@results = {"x"=>"1"}
|
164
|
+
expect{subject}.not_to raise_error
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context "lookups" do
|
169
|
+
it "matches" do
|
170
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
171
|
+
{'rule_type'=>'SimpleRule'},
|
172
|
+
{'g_array'=>'G1V3'})
|
173
|
+
expect(lookup.to_a.count).to eq(1)
|
174
|
+
expect(lookup.first.name).to eq("Rule1")
|
175
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
176
|
+
{'rule_type'=>'SimpleRule',
|
177
|
+
'other_flag'=>true},
|
178
|
+
{})
|
179
|
+
expect(lookup.to_a.count).to eq(3)
|
180
|
+
expect(lookup.map{|l|l.name}.to_set).to eq(Set["Rule2","Rule2a","Rule2b"])
|
181
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
182
|
+
{'rule_type'=>'ComplexRule',
|
183
|
+
'other_flag'=>false},
|
184
|
+
{})
|
185
|
+
expect(lookup.to_a.count).to eq(1)
|
186
|
+
expect(lookup.first.name).to eq("Rule3")
|
187
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
188
|
+
{'rule_type'=>'ComplexRule'},
|
189
|
+
{'g_string'=>'def'})
|
190
|
+
expect(lookup.to_a.count).to eq(1)
|
191
|
+
expect(lookup.first.name).to eq("Rule3")
|
192
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
193
|
+
{'rule_type'=>'ComplexRule'},
|
194
|
+
{'g_string'=>'abc'})
|
195
|
+
expect(lookup).to eq([])
|
196
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
197
|
+
{'rule_type'=>'SimpleRule'},
|
198
|
+
{'g_bool'=>true, "g_range"=>25,
|
199
|
+
'g_integer'=>99})
|
200
|
+
expect(lookup.to_a.count).to eq(1)
|
201
|
+
expect(lookup.first.name).to eq("Rule2a")
|
202
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
203
|
+
{'rule_type'=>'SimpleRule'},
|
204
|
+
{'g_bool'=>true, "g_range"=>75})
|
205
|
+
expect(lookup.to_a.count).to eq(1)
|
206
|
+
expect(lookup.first.name).to eq("Rule1")
|
207
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
208
|
+
{'rule_type'=>'SimpleRule'},
|
209
|
+
{'g_bool'=>true, "g_range"=>75,
|
210
|
+
'g_integer'=>11})
|
211
|
+
expect(lookup).to eq([])
|
212
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
213
|
+
{'rule_type'=>'SimpleRule'},
|
214
|
+
{'g_bool'=>true, "g_range"=>75,
|
215
|
+
'g_integer'=>10})
|
216
|
+
expect(lookup.to_a.count).to eq(1)
|
217
|
+
expect(lookup.first.name).to eq("Rule1")
|
218
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
219
|
+
{'rule_type'=>'SimpleRule'},
|
220
|
+
{'g_bool'=>false, "g_range"=>75,
|
221
|
+
'g_integer'=>10})
|
222
|
+
expect(lookup).to eq([])
|
223
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
224
|
+
{'rule_type'=>'SimpleRule'}, {})
|
225
|
+
expect(lookup.to_a.count).to eq(4)
|
226
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
227
|
+
{'rule_dt'=>"2017-3-1 02:00:00"},
|
228
|
+
{})
|
229
|
+
expect(lookup.to_a.count).to eq(5)
|
230
|
+
expect(lookup.pluck(:name).to_set).to eq(Set["Rule1", "Rule2", "Rule2a",
|
231
|
+
"Rule2b", "Rule3"])
|
232
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
233
|
+
{'rule_dt'=>"2017-4-1 16:00:00"},
|
234
|
+
{})
|
235
|
+
expect(lookup.to_a.count).to eq(1)
|
236
|
+
expect(lookup.pluck(:name).first).to eq("Rule4")
|
237
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
238
|
+
{'rule_dt'=>"2016-12-31"}, {})
|
239
|
+
expect(lookup.to_a).to eq([])
|
240
|
+
lookup = Gemini::MyRule.get_matches('infinity',
|
241
|
+
{'rule_dt'=>"2017-5-1 00:00:01"}, {})
|
242
|
+
expect(lookup.to_a).to eq([])
|
243
|
+
end
|
244
|
+
end
|
245
|
+
context "rule compute" do
|
246
|
+
let(:complex) { Gemini::MyRule.get_matches('infinity',
|
247
|
+
{'rule_type'=>'ComplexRule'},
|
248
|
+
{'g_string'=>'def'}).first }
|
249
|
+
let(:xyz) { Gemini::XyzRule.get_matches('infinity',
|
250
|
+
{'rule_type'=>'ZRule'},
|
251
|
+
{'g_integer'=> 2}).first }
|
252
|
+
let(:simple) {
|
253
|
+
Gemini::MyRule.get_matches('infinity',
|
254
|
+
{'rule_type'=>'SimpleRule'},
|
255
|
+
{'g_bool'=>true, "g_range"=>25}).first }
|
256
|
+
let(:simple2a) {
|
257
|
+
Gemini::MyRule.get_matches('infinity',
|
258
|
+
{'rule_type'=>'SimpleRule'},
|
259
|
+
{'g_bool'=>true, "g_integer"=>99}).first }
|
260
|
+
let(:simple2b) {
|
261
|
+
Gemini::MyRule.get_matches('infinity',
|
262
|
+
{'rule_type'=>'SimpleRule'},
|
263
|
+
{'g_bool'=>true, "g_integer"=>999}).first }
|
264
|
+
let(:altgridmethod) {
|
265
|
+
Gemini::MyRule.get_matches('infinity',
|
266
|
+
{'rule_type'=>'ComplexRule'},
|
267
|
+
{"g_integer"=>3757}).first }
|
268
|
+
it "computed guards work" do
|
269
|
+
c = complex.compute({"pt"=>Time.zone.now,
|
270
|
+
'param2'=>'def'})
|
271
|
+
expect(c).to eq({"cguard2"=>false})
|
272
|
+
end
|
273
|
+
it "returns simple results via #fixed_results" do
|
274
|
+
expect(simple.fixed_results["simple_result"]).to eq("b value")
|
275
|
+
expect(simple.fixed_results["sr2"]).to eq(true)
|
276
|
+
expect(simple.fixed_results["sr3"]).to eq(123)
|
277
|
+
ssq = "string with single quotes"
|
278
|
+
expect(simple.fixed_results["single_quote"]).to eq(ssq)
|
279
|
+
swh = " string that contains a # character"
|
280
|
+
expect(simple.fixed_results["stringwithhash"]).to eq(swh)
|
281
|
+
expect(simple.fixed_results.count).to eq(5)
|
282
|
+
allow_any_instance_of(Delorean::Engine).
|
283
|
+
to receive(:evaluate).and_raise('hi mom')
|
284
|
+
expect{simple.compute({"pt"=>Time.now})}.to raise_error(/hi mom/)
|
285
|
+
# simple2a should return results without evaluation (they are all fixed)
|
286
|
+
expect(simple2a.compute({"pt"=>Time.zone.now})).to eq(
|
287
|
+
{"simple_result"=>"b value",
|
288
|
+
"sr2"=>true,
|
289
|
+
"sr3"=>123})
|
290
|
+
# simple2b should return grid results without evaluation
|
291
|
+
expect(simple2b.compute({"pt"=>Time.zone.now,
|
292
|
+
'param1'=> 66,
|
293
|
+
'param2'=>'abc',
|
294
|
+
'paramb'=>false})).to eq({"grid1_grid"=>3,
|
295
|
+
"grid2_grid"=>1300})
|
296
|
+
|
297
|
+
end
|
298
|
+
it "returns computed results" do
|
299
|
+
c = complex.compute({"pt"=>Time.zone.now,
|
300
|
+
'param1'=> 66,
|
301
|
+
'param2'=>'abc',
|
302
|
+
'paramb'=>false})
|
303
|
+
expect(c).to eq({"simple_result"=>"c value",
|
304
|
+
"computed_value"=>19, "grid1_grid"=>3, "grid2_grid"=>1300})
|
305
|
+
end
|
306
|
+
it "returns computed results (with delorean import)" do
|
307
|
+
c = xyz.compute({"pt"=>Time.zone.now+1,
|
308
|
+
"p1"=>12,
|
309
|
+
"p2"=>3,
|
310
|
+
"flavor"=>"cherry"})
|
311
|
+
expect(c).to eq({"bvlength"=>13,"bv"=>"cherry --> 36",
|
312
|
+
"grid1_grid"=>19})
|
313
|
+
end
|
314
|
+
it "grids embedded in result work properly and receive prior attrs" do
|
315
|
+
v = altgridmethod.compute({"pt"=>Time.zone.now,
|
316
|
+
'param1'=> 45,
|
317
|
+
'param2' => 1})
|
318
|
+
expect(v["final_value"]).to eq(15)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|