marty 1.2.0 → 1.2.1

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +2 -1
  3. data/Gemfile.lock +2 -2
  4. data/app/components/marty/base_rule_view.rb +279 -0
  5. data/app/components/marty/delorean_rule_view.rb +26 -0
  6. data/app/components/marty/extras/layout.rb +22 -7
  7. data/app/components/marty/log_view.rb +1 -1
  8. data/app/components/marty/mcfly_grid_panel.rb +53 -0
  9. data/app/components/marty/mcfly_grid_panel/client/dup_in_form.js +20 -0
  10. data/app/models/marty/base_rule.rb +126 -0
  11. data/app/models/marty/delorean_rule.rb +121 -0
  12. data/lib/marty/data_importer.rb +0 -1
  13. data/lib/marty/rule_script_set.rb +176 -0
  14. data/lib/marty/version.rb +1 -1
  15. data/lib/tasks/marty_tasks.rake +42 -0
  16. data/spec/dummy/app/components/gemini/cm_auth_app.rb +18 -0
  17. data/spec/dummy/app/components/gemini/my_rule_view.rb +63 -0
  18. data/spec/dummy/app/components/gemini/xyz_rule_view.rb +25 -0
  19. data/spec/dummy/app/models/gemini/guard_one.rb +5 -0
  20. data/spec/dummy/app/models/gemini/guard_two.rb +5 -0
  21. data/spec/dummy/app/models/gemini/my_rule.rb +46 -0
  22. data/spec/dummy/app/models/gemini/my_rule_type.rb +5 -0
  23. data/spec/dummy/app/models/gemini/xyz_enum.rb +5 -0
  24. data/spec/dummy/app/models/gemini/xyz_rule.rb +63 -0
  25. data/spec/dummy/app/models/gemini/xyz_rule_type.rb +5 -0
  26. data/spec/dummy/config/locales/en.yml +10 -0
  27. data/spec/dummy/db/migrate/20171220150101_add_rule_type_enums.rb +14 -0
  28. data/spec/dummy/db/migrate/20171221095312_create_gemini_my_rules.rb +22 -0
  29. data/spec/dummy/db/migrate/20171221095359_create_gemini_xyz_rules.rb +21 -0
  30. data/spec/dummy/db/migrate/20171222150100_add_rule_indices.rb +34 -0
  31. data/spec/dummy/db/seeds.rb +1 -1
  32. data/spec/dummy/delorean/base_code.dl +6 -0
  33. data/spec/dummy/lib/gemini/my_rule_script_set.rb +13 -0
  34. data/spec/dummy/lib/gemini/xyz_rule_script_set.rb +22 -0
  35. data/spec/features/rule_spec.rb +265 -0
  36. data/spec/fixtures/csv/rule/DataGrid.csv +6 -0
  37. data/spec/fixtures/csv/rule/MyRule.csv +14 -0
  38. data/spec/fixtures/csv/rule/XyzRule.csv +6 -0
  39. data/spec/models/rule_spec.rb +322 -0
  40. data/spec/support/integration_helpers.rb +1 -0
  41. metadata +29 -2
@@ -0,0 +1,13 @@
1
+ class Gemini::MyRuleScriptSet < Marty::RuleScriptSet
2
+ def self.node_name
3
+ "Node2"
4
+ end
5
+ def self.body_start
6
+ params = <<~END
7
+ param1 =?
8
+ param2 =?
9
+ paramb =? false
10
+ END
11
+ super + indent(params)
12
+ end
13
+ end
@@ -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