marty 2.9.3 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitlab-ci.yml +6 -5
  3. data/Gemfile.lock +1 -1
  4. data/app/components/marty/data_grid_view.rb +64 -1
  5. data/app/components/marty/data_grid_view/client/data_grid_edit.js +550 -0
  6. data/app/components/marty/import_type_view.rb +11 -2
  7. data/app/components/marty/main_auth_app.rb +23 -23
  8. data/app/components/marty/promise_view.rb +1 -1
  9. data/app/components/marty/report_select.rb +1 -0
  10. data/app/components/marty/script_form.rb +1 -1
  11. data/app/components/marty/user_view.rb +30 -18
  12. data/app/models/marty/data_grid.rb +34 -8
  13. data/app/models/marty/import_type.rb +2 -4
  14. data/app/models/marty/role_type.rb +10 -0
  15. data/app/models/marty/user.rb +9 -4
  16. data/app/models/marty/user_role.rb +2 -3
  17. data/app/models/marty/vw_promise.rb +2 -2
  18. data/app/services/marty/data_grid/constraint.rb +73 -0
  19. data/app/services/marty/data_grid_view/save_grid.rb +63 -0
  20. data/config/locales/en.yml +1 -0
  21. data/db/migrate/107_add_data_grid_constraint.rb +8 -0
  22. data/db/migrate/507_migrate_marty_roles_to_enum.rb +72 -0
  23. data/db/seeds.rb +1 -6
  24. data/lib/marty/permissions.rb +6 -16
  25. data/lib/marty/version.rb +1 -1
  26. data/spec/dummy/config/locales/en.yml +1 -0
  27. data/spec/features/data_grid_spec.rb +499 -0
  28. data/spec/features/data_import_spec.rb +11 -8
  29. data/spec/features/user_view_spec.rb +1 -1
  30. data/spec/fixtures/json/data_grid.json +210 -0
  31. data/spec/fixtures/misc/data_grid_1.txt +15 -0
  32. data/spec/fixtures/misc/data_grid_2.txt +17 -0
  33. data/spec/fixtures/misc/data_grid_3.txt +9 -0
  34. data/spec/fixtures/misc/data_grid_4.txt +5 -0
  35. data/spec/fixtures/misc/data_grid_5.txt +19 -0
  36. data/spec/fixtures/misc/grid1_final_data.json +23 -0
  37. data/spec/fixtures/misc/grid1_final_meta.json +182 -0
  38. data/spec/fixtures/misc/grid2_final_data.json +11 -0
  39. data/spec/fixtures/misc/grid2_final_meta.json +181 -0
  40. data/spec/fixtures/misc/grid5_final_data.json +142 -0
  41. data/spec/fixtures/misc/grid5_final_meta.json +152 -0
  42. data/spec/fixtures/misc/grid_log_errs.json +418 -0
  43. data/spec/models/data_grid_spec.rb +689 -626
  44. data/spec/models/import_type_spec.rb +5 -5
  45. data/spec/spec_helper.rb +9 -7
  46. data/spec/support/users.rb +1 -1
  47. metadata +22 -3
  48. data/app/models/marty/role.rb +0 -6
@@ -0,0 +1,418 @@
1
+ [
2
+ {
3
+ "message": "save_grid: entered with view permissions",
4
+ "details": {
5
+ "data": [
6
+ {
7
+ "a0": "",
8
+ "a1": "",
9
+ "a2": "Conventional",
10
+ "a3": "Conventional",
11
+ "a4": "FHA",
12
+ "a5": "FHA",
13
+ "a6": "FHA",
14
+ "a7": "FHA",
15
+ "a8": "VA",
16
+ "a9": "VA",
17
+ "a10": "VA",
18
+ "a11": "VA",
19
+ "a12": "VA",
20
+ "a13": "VA"
21
+ },
22
+ {
23
+ "a0": "",
24
+ "a1": "",
25
+ "a2": null,
26
+ "a3": null,
27
+ "a4": null,
28
+ "a5": "Condominium",
29
+ "a6": null,
30
+ "a7": null,
31
+ "a8": "Condominium",
32
+ "a9": null,
33
+ "a10": null,
34
+ "a11": null,
35
+ "a12": null,
36
+ "a13": null
37
+ },
38
+ {
39
+ "a0": "",
40
+ "a1": "",
41
+ "a2": "<=1",
42
+ "a3": ">1",
43
+ "a4": "<1",
44
+ "a5": ">=1",
45
+ "a6": "<=1",
46
+ "a7": ">1",
47
+ "a8": ">=1",
48
+ "a9": "<=1",
49
+ "a10": ">1<=2",
50
+ "a11": ">2<=3",
51
+ "a12": ">3<=4",
52
+ "a13": ">5"
53
+ },
54
+ {
55
+ "a0": "AK",
56
+ "a1": null,
57
+ "a2": "123.456",
58
+ "a3": 2.2,
59
+ "a4": 100,
60
+ "a5": 3.3,
61
+ "a6": 4.4,
62
+ "a7": 5.5,
63
+ "a8": 6.6,
64
+ "a9": 7.7,
65
+ "a10": 8.8,
66
+ "a11": 9.9,
67
+ "a12": 10.1,
68
+ "a13": 100
69
+ },
70
+ {
71
+ "a0": "AR",
72
+ "a1": "Dallas",
73
+ "a2": 23.23,
74
+ "a3": 24.24,
75
+ "a4": 100,
76
+ "a5": 25.25,
77
+ "a6": 26.26,
78
+ "a7": 27.27,
79
+ "a8": 28.28,
80
+ "a9": 29.29,
81
+ "a10": 30.3,
82
+ "a11": 31.31,
83
+ "a12": 32.32,
84
+ "a13": 100
85
+ },
86
+ {
87
+ "a0": "AR",
88
+ "a1": "Montgomery",
89
+ "a2": 34.34,
90
+ "a3": 35.35,
91
+ "a4": 100,
92
+ "a5": 36.36,
93
+ "a6": 37.37,
94
+ "a7": 38.38,
95
+ "a8": 39.39,
96
+ "a9": 40.4,
97
+ "a10": 41.41,
98
+ "a11": 42.42,
99
+ "a12": 43.43,
100
+ "a13": 100
101
+ },
102
+ {
103
+ "a0": "AR",
104
+ "a1": "Nevada",
105
+ "a2": 45.45,
106
+ "a3": 46.46,
107
+ "a4": 100,
108
+ "a5": 47.47,
109
+ "a6": 48.48,
110
+ "a7": 49.49,
111
+ "a8": 50.5,
112
+ "a9": 51.51,
113
+ "a10": 52.52,
114
+ "a11": 53.53,
115
+ "a12": 54.54,
116
+ "a13": 100
117
+ },
118
+ {
119
+ "a0": "AR",
120
+ "a1": "Newton",
121
+ "a2": 56.56,
122
+ "a3": 57.57,
123
+ "a4": 100,
124
+ "a5": 58.58,
125
+ "a6": 59.59,
126
+ "a7": 60.6,
127
+ "a8": 61.61,
128
+ "a9": 62.62,
129
+ "a10": 63.63,
130
+ "a11": 64.64,
131
+ "a12": 65.65,
132
+ "a13": 100
133
+ },
134
+ {
135
+ "a0": "AR",
136
+ "a1": "Pike",
137
+ "a2": 67.67,
138
+ "a3": 68.68,
139
+ "a4": 100,
140
+ "a5": 69.69,
141
+ "a6": 70.7,
142
+ "a7": 71.71,
143
+ "a8": 72.72,
144
+ "a9": 73.73,
145
+ "a10": 74.74,
146
+ "a11": 75.75,
147
+ "a12": 76.76,
148
+ "a13": 100
149
+ },
150
+ {
151
+ "a0": "AR",
152
+ "a1": "Abc",
153
+ "a2": 2,
154
+ "a3": 3,
155
+ "a4": 100,
156
+ "a5": 4,
157
+ "a6": 5,
158
+ "a7": 6,
159
+ "a8": 7,
160
+ "a9": 8,
161
+ "a10": 9,
162
+ "a11": 10,
163
+ "a12": 11,
164
+ "a13": 100
165
+ },
166
+ {
167
+ "a0": "AR",
168
+ "a1": null,
169
+ "a2": 78.78,
170
+ "a3": 79.79,
171
+ "a4": 100,
172
+ "a5": 80.8,
173
+ "a6": 81.81,
174
+ "a7": 82.82,
175
+ "a8": 83.83,
176
+ "a9": 84.84,
177
+ "a10": 85.85,
178
+ "a11": 86.86,
179
+ "a12": 87.87,
180
+ "a13": 100
181
+ },
182
+ {
183
+ "a0": "TN",
184
+ "a1": "Xyz",
185
+ "a2": 10,
186
+ "a3": 9,
187
+ "a4": 100,
188
+ "a5": 8,
189
+ "a6": 7,
190
+ "a7": 6,
191
+ "a8": 5,
192
+ "a9": 4,
193
+ "a10": 3,
194
+ "a11": 2,
195
+ "a12": 1,
196
+ "a13": 100
197
+ },
198
+ {
199
+ "a0": "AZ",
200
+ "a1": "Apache",
201
+ "a2": 89.89,
202
+ "a3": 90.9,
203
+ "a4": 100,
204
+ "a5": 91.91,
205
+ "a6": 92.92,
206
+ "a7": 93.93,
207
+ "a8": 94.94,
208
+ "a9": 95.95,
209
+ "a10": 96.96,
210
+ "a11": 97.97,
211
+ "a12": 98.98,
212
+ "a13": 100
213
+ }
214
+ ],
215
+ "perm": "view"
216
+ }
217
+ },
218
+ {
219
+ "message": "save_grid: grid modification not allowed",
220
+ "details": {
221
+ "data": [
222
+ [
223
+ "",
224
+ "",
225
+ "Conventional",
226
+ "Conventional",
227
+ "FHA",
228
+ "FHA",
229
+ "FHA",
230
+ "FHA",
231
+ "VA",
232
+ "VA",
233
+ "VA",
234
+ "VA",
235
+ "VA",
236
+ "VA"
237
+ ],
238
+ [
239
+ "",
240
+ "",
241
+ null,
242
+ null,
243
+ null,
244
+ "Condominium",
245
+ null,
246
+ null,
247
+ "Condominium",
248
+ null,
249
+ null,
250
+ null,
251
+ null,
252
+ null
253
+ ],
254
+ [
255
+ "",
256
+ "",
257
+ "<=1",
258
+ ">1",
259
+ "<1",
260
+ ">=1",
261
+ "<=1",
262
+ ">1",
263
+ ">=1",
264
+ "<=1",
265
+ ">1<=2",
266
+ ">2<=3",
267
+ ">3<=4",
268
+ ">5"
269
+ ],
270
+ [
271
+ "AK",
272
+ null,
273
+ "123.456",
274
+ 2.2,
275
+ 100,
276
+ 3.3,
277
+ 4.4,
278
+ 5.5,
279
+ 6.6,
280
+ 7.7,
281
+ 8.8,
282
+ 9.9,
283
+ 10.1,
284
+ 100
285
+ ],
286
+ [
287
+ "AR",
288
+ "Montgomery",
289
+ 34.34,
290
+ 35.35,
291
+ 100,
292
+ 36.36,
293
+ 37.37,
294
+ 38.38,
295
+ 39.39,
296
+ 40.4,
297
+ 41.41,
298
+ 42.42,
299
+ 43.43,
300
+ 100
301
+ ],
302
+ [
303
+ "AR",
304
+ "Nevada",
305
+ 45.45,
306
+ 46.46,
307
+ 100,
308
+ 47.47,
309
+ 48.48,
310
+ 49.49,
311
+ 50.5,
312
+ 51.51,
313
+ 52.52,
314
+ 53.53,
315
+ 54.54,
316
+ 100
317
+ ],
318
+ [
319
+ "AR",
320
+ "Newton",
321
+ 56.56,
322
+ 57.57,
323
+ 100,
324
+ 58.58,
325
+ 59.59,
326
+ 60.6,
327
+ 61.61,
328
+ 62.62,
329
+ 63.63,
330
+ 64.64,
331
+ 65.65,
332
+ 100
333
+ ],
334
+ [
335
+ "AR",
336
+ "Pike",
337
+ 67.67,
338
+ 68.68,
339
+ 100,
340
+ 69.69,
341
+ 70.7,
342
+ 71.71,
343
+ 72.72,
344
+ 73.73,
345
+ 74.74,
346
+ 75.75,
347
+ 76.76,
348
+ 100
349
+ ],
350
+ [
351
+ "AR",
352
+ "Abc",
353
+ 2,
354
+ 3,
355
+ 100,
356
+ 4,
357
+ 5,
358
+ 6,
359
+ 7,
360
+ 8,
361
+ 9,
362
+ 10,
363
+ 11,
364
+ 100
365
+ ],
366
+ [
367
+ "AR",
368
+ null,
369
+ 78.78,
370
+ 79.79,
371
+ 100,
372
+ 80.8,
373
+ 81.81,
374
+ 82.82,
375
+ 83.83,
376
+ 84.84,
377
+ 85.85,
378
+ 86.86,
379
+ 87.87,
380
+ 100
381
+ ],
382
+ [
383
+ "TN",
384
+ "Xyz",
385
+ 10,
386
+ 9,
387
+ 100,
388
+ 8,
389
+ 7,
390
+ 6,
391
+ 5,
392
+ 4,
393
+ 3,
394
+ 2,
395
+ 1,
396
+ 100
397
+ ],
398
+ [
399
+ "AZ",
400
+ "Apache",
401
+ 89.89,
402
+ 90.9,
403
+ 100,
404
+ 91.91,
405
+ 92.92,
406
+ 93.93,
407
+ 94.94,
408
+ 95.95,
409
+ 96.96,
410
+ 97.97,
411
+ 98.98,
412
+ 100
413
+ ]
414
+ ],
415
+ "perm": "edit_data"
416
+ }
417
+ }
418
+ ]
@@ -1,8 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
- module Marty::DataGridSpec
3
+ module Marty::DataGridSpec # rubocop:disable Metrics/ModuleLength
4
4
  describe DataGrid do
5
- G1 = <<EOS
5
+ G1 = <<EOS
6
6
  state\tstring\tv\t\t
7
7
  ltv\tnumrange\tv\t\t
8
8
  fico\tnumrange\th\t\t
@@ -15,7 +15,7 @@ MA\t>80<=105\t4.5\t5.6\t
15
15
  \t<=80\t11\t22\t33
16
16
  EOS
17
17
 
18
- G2 = <<EOS
18
+ G2 = <<EOS
19
19
  units\tinteger\tv\t\t
20
20
  ltv\tnumrange\tv\t\t
21
21
  cltv\tnumrange\th\t\t
@@ -29,9 +29,9 @@ fico\tnumrange\th\t\t
29
29
  3|4\t>80<=105\t4.5\t5.6\t6.7
30
30
  EOS
31
31
 
32
- G3 = File.open(File.expand_path('../srp_data.csv', __FILE__)).read
32
+ G3 = File.open(File.expand_path('../srp_data.csv', __FILE__)).read
33
33
 
34
- G4 = <<EOS
34
+ G4 = <<EOS
35
35
  lenient
36
36
  hb_indicator\tboolean\tv
37
37
  cltv\tnumrange\th
@@ -40,21 +40,21 @@ cltv\tnumrange\th
40
40
  true\t-0.750\t-0.750\t-0.750\t-1.500\t-1.500\t-1.500\t\t
41
41
  EOS
42
42
 
43
- G5 = <<EOS
43
+ G5 = <<EOS
44
44
  ltv\tnumrange\tv\t\t
45
45
 
46
46
  <=115\t-0.375
47
47
  >115<=135\t-0.750
48
48
  EOS
49
49
 
50
- G6 = <<EOS
50
+ G6 = <<EOS
51
51
  ltv\tnumrange\th
52
52
 
53
53
  <=115\t>115<=135
54
54
  -0.375\t-0.750
55
55
  EOS
56
56
 
57
- G7 = <<EOS
57
+ G7 = <<EOS
58
58
  string
59
59
  hb_indicator\tboolean\tv
60
60
  cltv\tnumrange\th
@@ -63,7 +63,7 @@ cltv\tnumrange\th
63
63
  true\tThis\tis\ta\ttest\tof\tstring type\t\t
64
64
  EOS
65
65
 
66
- G8 = <<EOS
66
+ G8 = <<EOS
67
67
  Marty::DataGrid
68
68
  ltv\tnumrange\tv\t\t
69
69
 
@@ -72,7 +72,7 @@ ltv\tnumrange\tv\t\t
72
72
  >135<=140\tG3
73
73
  EOS
74
74
 
75
- G9 = <<EOS
75
+ G9 = <<EOS
76
76
  state\tstring\tv
77
77
  ltv\tnumrange\tv
78
78
 
@@ -80,14 +80,14 @@ CA|TX\t>80\t123
80
80
  \t>80\t456
81
81
  EOS
82
82
 
83
- Ga = <<EOS
83
+ Ga = <<EOS
84
84
  dg\tMarty::DataGrid\tv\t\t
85
85
 
86
86
  G1|G2\t7
87
87
  G3\t8
88
88
  EOS
89
89
 
90
- Gb = <<EOS
90
+ Gb = <<EOS
91
91
  property_state\tGemini::State\tv\t\t
92
92
 
93
93
  CA|TX\t70
@@ -95,28 +95,28 @@ GA\t80
95
95
  MN\t90
96
96
  EOS
97
97
 
98
- Gc = <<EOS
98
+ Gc = <<EOS
99
99
  Marty::DataGrid
100
100
  property_state\tGemini::State\tv\t\t
101
101
 
102
102
  CA|TX\tGb
103
103
  EOS
104
104
 
105
- Gd = <<EOS
105
+ Gd = <<EOS
106
106
  hb_indicator\tboolean\tv
107
107
 
108
108
  true\t456
109
109
  false\t123
110
110
  EOS
111
111
 
112
- Ge = <<EOS
112
+ Ge = <<EOS
113
113
  ltv\tnumrange\th
114
114
 
115
115
  >110\t>120
116
116
  1.1\t1.1
117
117
  EOS
118
118
 
119
- Gf = <<EOS
119
+ Gf = <<EOS
120
120
  lenient string
121
121
  b\tboolean\tv
122
122
  i\tinteger\tv
@@ -128,7 +128,7 @@ true\t1\t<10\t<10.0\tY
128
128
  false\t\t>10\t\tN
129
129
  EOS
130
130
 
131
- Gg = <<EOS
131
+ Gg = <<EOS
132
132
  lenient
133
133
  i1\tinteger\tv
134
134
  i2\tinteger\tv
@@ -138,7 +138,7 @@ i2\tinteger\tv
138
138
  2\t\t20
139
139
  EOS
140
140
 
141
- Gh = <<EOS
141
+ Gh = <<EOS
142
142
  lenient
143
143
  property_state\tstring\tv
144
144
  county_name\tstring\tv
@@ -147,7 +147,7 @@ NY\t\t10
147
147
  \tR\t8
148
148
  EOS
149
149
 
150
- Gi = <<EOS
150
+ Gi = <<EOS
151
151
  units\tinteger\tv\t\t
152
152
  ltv\tfloat\tv\t\t
153
153
  cltv\tfloat\th\t\t
@@ -161,7 +161,7 @@ fico\tnumrange\th\t\t
161
161
  3|4\t105.5\t4.5\t5.6\t6.7
162
162
  EOS
163
163
 
164
- Gj = <<EOS
164
+ Gj = <<EOS
165
165
  lenient
166
166
  client_id\tinteger\tv
167
167
  property_state\tstring\tv
@@ -170,7 +170,7 @@ property_state\tstring\tv
170
170
  700127\tCA\t0.35
171
171
  EOS
172
172
 
173
- Gk = <<EOS
173
+ Gk = <<EOS
174
174
  fha_203k_option\tstring\tv\tfha_203k_option
175
175
 
176
176
  Investor Services\t-0.625
@@ -179,632 +179,695 @@ Admin Services Plus\t-1.625
179
179
  Investor Services Acadamy\t-0.5
180
180
  EOS
181
181
 
182
- before(:each) do
183
- # Mcfly.whodunnit = Marty::User.find_by_login('marty')
184
- marty_whodunnit
185
- end
186
-
187
- def lookup_grid_helper(pt, gridname, params, follow = false, distinct = true)
188
- dgh = Marty::DataGrid.lookup_h(pt, gridname)
189
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt, params, dgh, nil, follow,
190
- false, distinct)
191
- [res['result'], res['name']]
192
- end
193
-
194
- describe 'imports' do
195
- it 'should not allow imports with trailing blank columns' do
196
- expect do
197
- dg_from_import('G1', G1.gsub("\n", "\t\n"))
198
- end.to raise_error(RuntimeError)
199
- end
200
-
201
- it 'should not allow imports with last blank row' do
202
- expect do
203
- dg_from_import('Gh', Gh + "\t\t\n")
204
- end.to raise_error(RuntimeError)
205
- end
206
- end
207
-
208
- describe 'validations' do
209
- it 'should not allow bad axis types' do
210
- expect do
211
- dg_from_import('Gi', Gi)
212
- end.to raise_error(/unknown metadata type float/)
213
- expect do
214
- dg_from_import('Gi', Gi.sub(/float/, 'abcdef'))
215
- end.to raise_error(/unknown metadata type abcdef/)
216
- end
217
-
218
- it 'should not allow dup attr names' do
219
- g_bad = G1.sub(/fico/, 'ltv')
220
-
221
- expect do
222
- dg_from_import('G2', g_bad)
223
- end.to raise_error(ActiveRecord::RecordInvalid)
224
- end
225
-
226
- it 'should not allow dup grid names' do
227
- dg_from_import('G1', G1)
228
-
229
- expect do
230
- dg_from_import('G1', G2)
231
- end.to raise_error(ActiveRecord::RecordInvalid)
232
- end
233
-
234
- it 'should not allow extra attr rows' do
235
- g_bad = "x\tnumrange\th\t\t\n" + G1
236
-
237
- expect do
238
- dg_from_import('G2', g_bad)
239
- end.to raise_error(RuntimeError)
240
- end
241
-
242
- it 'should not allow dup row/col key combos' do
243
- g_bad = G1 + G1.split("\n").last + "\n"
244
- expect do
245
- dg_from_import('G2', g_bad)
246
- end.to raise_error(ActiveRecord::RecordInvalid)
247
-
248
- g_bad = G2 + G2.split("\n").last + "\n"
249
- expect do
250
- dg_from_import('G2', g_bad)
251
- end.to raise_error(ActiveRecord::RecordInvalid)
252
- end
253
-
254
- it 'Unknown keys for typed grids should raise error' do
255
- g_bad = G8.sub(/G3/, 'XXXXX')
256
-
257
- expect do
258
- dg_from_import('G8', g_bad)
259
- end.to raise_error(RuntimeError)
260
-
261
- g_bad = G8.sub(/DataGrid/, 'Division')
262
-
263
- expect do
264
- dg_from_import('G8', g_bad)
265
- end.to raise_error(RuntimeError)
266
- end
267
-
268
- it 'Unknown keys for grid headers should raise error' do
269
- g_bad = Ga.sub(/G3/, 'XXXXX')
270
-
271
- expect do
272
- dg_from_import('Ga', g_bad)
273
- end.to raise_error(RuntimeError)
274
-
275
- g_bad = Ga.sub(/DataGrid/, 'Division')
276
-
277
- expect do
278
- dg_from_import('Ga', g_bad)
279
- end.to raise_error(RuntimeError)
280
- end
281
-
282
- it 'validates grid modifier' do
283
- bad = ': abc def'
284
- g_bad = Gk.sub(/fha_203k_option$/, bad)
285
- expect do
286
- dg_from_import('Gk', g_bad)
287
- end.to raise_error(/invalid grid modifier expression: #{bad}/)
288
- expect do
289
- dg_from_import('Gk', Gk)
290
- end.not_to raise_error
291
- end
292
- end
293
-
294
- describe 'lookups for infinity' do
295
- let(:pt) { 'infinity' }
296
-
297
- before(:each) do
298
- ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'Ga', 'Gb',
299
- 'Gc', 'Gd', 'Ge', 'Gf', 'Gg', 'Gh', 'Gj'].each do |g|
300
- dg_from_import(g, "Marty::DataGridSpec::#{g}".constantize)
182
+ before(:each) do
183
+ # Mcfly.whodunnit = Marty::User.find_by_login('marty')
184
+ marty_whodunnit
301
185
  end
302
- end
303
-
304
- context 'should handle NULL key values' do
305
- let(:dgh) { 'Gf' }
306
186
 
307
- it 'true returns Y' do
308
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'b' => true }, false)
309
- expect(res).to eq('Y')
187
+ def lookup_grid_helper(pt, gridname, params, follow = false, distinct = true)
188
+ dgh = Marty::DataGrid.lookup_h(pt, gridname)
189
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt, params, dgh, nil, follow,
190
+ false, distinct)
191
+ [res['result'], res['name']]
310
192
  end
311
193
 
312
- it '13 returns N' do
313
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13 }, true)
314
- expect(res).to eq('N')
194
+ describe 'imports' do
195
+ it 'should not allow imports with trailing blank columns' do
196
+ expect do
197
+ dg_from_import('G1', G1.gsub("\n", "\t\n"))
198
+ end.to raise_error(RuntimeError)
199
+ end
200
+
201
+ it 'should not allow imports with last blank row' do
202
+ expect do
203
+ dg_from_import('Gh', Gh + "\t\t\n")
204
+ end.to raise_error(RuntimeError)
205
+ end
315
206
  end
316
207
 
317
- it '13 & numrange 0 returns nil' do
318
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'n' => 0 }, true)
319
- expect(res).to eq('N')
208
+ describe 'validations' do
209
+ it 'should not allow bad axis types' do
210
+ expect do
211
+ dg_from_import('Gi', Gi)
212
+ end.to raise_error(/unknown metadata type float/)
213
+ expect do
214
+ dg_from_import('Gi', Gi.sub(/float/, 'abcdef'))
215
+ end.to raise_error(/unknown metadata type abcdef/)
216
+ end
217
+
218
+ it 'should not allow dup attr names' do
219
+ g_bad = G1.sub(/fico/, 'ltv')
220
+
221
+ expect do
222
+ dg_from_import('G2', g_bad)
223
+ end.to raise_error(ActiveRecord::RecordInvalid)
224
+ end
225
+
226
+ it 'should not allow dup grid names' do
227
+ dg_from_import('G1', G1)
228
+
229
+ expect do
230
+ dg_from_import('G1', G2)
231
+ end.to raise_error(ActiveRecord::RecordInvalid)
232
+ end
233
+
234
+ it 'should not allow extra attr rows' do
235
+ g_bad = "x\tnumrange\th\t\t\n" + G1
236
+
237
+ expect do
238
+ dg_from_import('G2', g_bad)
239
+ end.to raise_error(RuntimeError)
240
+ end
241
+
242
+ it 'should not allow dup row/col key combos' do
243
+ g_bad = G1 + G1.split("\n").last + "\n"
244
+ expect do
245
+ dg_from_import('G2', g_bad)
246
+ end.to raise_error(ActiveRecord::RecordInvalid)
247
+
248
+ g_bad = G2 + G2.split("\n").last + "\n"
249
+ expect do
250
+ dg_from_import('G2', g_bad)
251
+ end.to raise_error(ActiveRecord::RecordInvalid)
252
+ end
253
+
254
+ it 'Unknown keys for typed grids should raise error' do
255
+ g_bad = G8.sub(/G3/, 'XXXXX')
256
+
257
+ expect do
258
+ dg_from_import('G8', g_bad)
259
+ end.to raise_error(RuntimeError)
260
+
261
+ g_bad = G8.sub(/DataGrid/, 'Division')
262
+
263
+ expect do
264
+ dg_from_import('G8', g_bad)
265
+ end.to raise_error(RuntimeError)
266
+ end
267
+
268
+ it 'Unknown keys for grid headers should raise error' do
269
+ g_bad = Ga.sub(/G3/, 'XXXXX')
270
+
271
+ expect do
272
+ dg_from_import('Ga', g_bad)
273
+ end.to raise_error(RuntimeError)
274
+
275
+ g_bad = Ga.sub(/DataGrid/, 'Division')
276
+
277
+ expect do
278
+ dg_from_import('Ga', g_bad)
279
+ end.to raise_error(RuntimeError)
280
+ end
281
+
282
+ it 'validates grid modifier' do
283
+ bad = ': abc def'
284
+ g_bad = Gk.sub(/fha_203k_option$/, bad)
285
+ expect do
286
+ dg_from_import('Gk', g_bad)
287
+ end.to raise_error(/invalid grid modifier expression: #{bad}/)
288
+ expect do
289
+ dg_from_import('Gk', Gk)
290
+ end.not_to raise_error
291
+ end
320
292
  end
321
293
 
322
- it '13 & int4range 15 returns N' do
323
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'i4' => 15 }, true)
324
- expect(res).to eq('N')
325
- end
326
-
327
- it '13 & int4range 1 returns nil' do
328
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'i4' => 1 }, true)
329
- expect(res).to be_nil
330
- end
331
-
332
- it 'false, 3, numrange 15 returns N' do
333
- res = Marty::DataGrid.
334
- lookup_grid_h(pt, dgh, { 'b' => false, 'i' => 3, 'n' => 15 }, true)
335
- expect(res).to eq('N')
336
- end
337
-
338
- it '13, numrange 15 returns N' do
339
- res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'n' => 15 }, true)
340
- expect(res).to eq('N')
341
- end
342
- end
343
-
344
- it 'should handle ambiguous lookups' do
345
- h1 = {
346
- 'property_state' => 'NY',
347
- 'county_name' => 'R',
348
- }
349
-
350
- res = Marty::DataGrid.lookup_grid_h(pt, 'Gh', h1, false)
351
- expect(res).to eq(10)
352
- end
353
-
354
- it 'should handle ambiguous lookups (2)' do
355
- res = Marty::DataGrid.
356
- lookup_grid_h(pt, 'Gg', { 'i1' => 2, 'i2' => 1 }, false)
357
- expect(res).to eq(1)
358
-
359
- res = Marty::DataGrid.
360
- lookup_grid_h(pt, 'Gg', { 'i1' => 3, 'i2' => 1 }, false)
361
- expect(res).to eq(1)
362
-
363
- res = Marty::DataGrid.
364
- lookup_grid_h(pt, 'Gg', { 'i1' => 2, 'i2' => 3 }, false)
365
- expect(res).to eq(20)
366
- end
367
-
368
- it 'should handle non-distinct lookups' do
369
- res = Marty::DataGrid.lookup_grid_h(pt, 'Ge', { 'ltv' => 500 }, false)
370
-
371
- expect(res).to eq(1.1)
372
-
373
- expect do
374
- Marty::DataGrid.lookup_grid_h(pt, 'Ge', { 'ltv' => 500 }, true)
375
- end.to raise_error(RuntimeError)
376
- end
377
-
378
- it 'should handle non-distinct lookups (2)' do
379
- params = {
380
- 'client_id' => 700127,
381
- 'property_state' => 'CA',
382
- }
383
- res = Marty::DataGrid.lookup_grid_h(pt, 'Gj', params, false)
384
-
385
- # should return the upper left corner match
386
- expect(res).to eq(0.25)
387
-
388
- expect do
389
- Marty::DataGrid.lookup_grid_h(pt, 'Gj', params, true)
390
- end.to raise_error(RuntimeError)
391
- end
392
-
393
- it 'should handle boolean lookups' do
394
- res = [true, false].map do |hb_indicator|
395
- lookup_grid_helper('infinity',
396
- 'Gd',
397
- 'hb_indicator' => hb_indicator,
398
- )
399
- end
400
- expect(res).to eq [[456.0, 'Gd'], [123.0, 'Gd']]
401
- end
402
-
403
- it 'should handle basic lookups' do
404
- res = lookup_grid_helper('infinity',
405
- 'G3',
406
- 'amount' => 160300,
407
- 'state' => 'HI',
408
- )
409
- expect(res).to eq [1.655, 'G3']
410
-
411
- [3, 4].each do |units|
412
- res = lookup_grid_helper('infinity',
413
- 'G2',
414
- 'fico' => 720,
415
- 'units' => units,
416
- 'ltv' => 100,
417
- 'cltv' => 110.1,
418
- )
419
- expect(res).to eq [5.6, 'G2']
420
- end
421
-
422
- dg = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
423
-
424
- h = {
425
- 'fico' => 600,
426
- 'state' => 'RI',
427
- 'ltv' => 10,
428
- }
429
-
430
- res = lookup_grid_helper('infinity', 'G1', h)
431
- expect(res).to eq [11, 'G1']
432
-
433
- dg.update_from_import('G1', G1.sub(/11/, '111'))
434
-
435
- res = lookup_grid_helper('infinity', 'G1', h)
436
- expect(res).to eq [111, 'G1']
437
- end
438
-
439
- it 'should result in error when there are multiple cell hits' do
440
- expect do
441
- lookup_grid_helper('infinity',
442
- 'G2',
443
- 'fico' => 720,
444
- 'ltv' => 100,
445
- 'cltv' => 110.1,
446
- )
447
- end.to raise_error(RuntimeError)
448
- end
449
-
450
- it 'should return nil when matching data grid cell is nil' do
451
- res = lookup_grid_helper('infinity',
452
- 'G1',
453
- 'fico' => 800,
454
- 'state' => 'MA',
455
- 'ltv' => 81,
294
+ describe 'lookups for infinity' do
295
+ let(:pt) { 'infinity' }
296
+
297
+ before(:each) do
298
+ ['G1', 'G2', 'G3', 'G4', 'G5', 'G6', 'G7', 'G8', 'Ga', 'Gb',
299
+ 'Gc', 'Gd', 'Ge', 'Gf', 'Gg', 'Gh', 'Gj'].each do |g|
300
+ dg_from_import(g, "Marty::DataGridSpec::#{g}".constantize)
301
+ end
302
+ end
303
+
304
+ context 'should handle NULL key values' do
305
+ let(:dgh) { 'Gf' }
306
+
307
+ it 'true returns Y' do
308
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'b' => true }, false)
309
+ expect(res).to eq('Y')
310
+ end
311
+
312
+ it '13 returns N' do
313
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13 }, true)
314
+ expect(res).to eq('N')
315
+ end
316
+
317
+ it '13 & numrange 0 returns nil' do
318
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'n' => 0 }, true)
319
+ expect(res).to eq('N')
320
+ end
321
+
322
+ it '13 & int4range 15 returns N' do
323
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'i4' => 15 }, true)
324
+ expect(res).to eq('N')
325
+ end
326
+
327
+ it '13 & int4range 1 returns nil' do
328
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'i4' => 1 }, true)
329
+ expect(res).to be_nil
330
+ end
331
+
332
+ it 'false, 3, numrange 15 returns N' do
333
+ res = Marty::DataGrid.
334
+ lookup_grid_h(pt, dgh, { 'b' => false, 'i' => 3, 'n' => 15 }, true)
335
+ expect(res).to eq('N')
336
+ end
337
+
338
+ it '13, numrange 15 returns N' do
339
+ res = Marty::DataGrid.lookup_grid_h(pt, dgh, { 'i' => 13, 'n' => 15 }, true)
340
+ expect(res).to eq('N')
341
+ end
342
+ end
343
+
344
+ it 'should handle ambiguous lookups' do
345
+ h1 = {
346
+ 'property_state' => 'NY',
347
+ 'county_name' => 'R',
348
+ }
349
+
350
+ res = Marty::DataGrid.lookup_grid_h(pt, 'Gh', h1, false)
351
+ expect(res).to eq(10)
352
+ end
353
+
354
+ it 'should handle ambiguous lookups (2)' do
355
+ res = Marty::DataGrid.
356
+ lookup_grid_h(pt, 'Gg', { 'i1' => 2, 'i2' => 1 }, false)
357
+ expect(res).to eq(1)
358
+
359
+ res = Marty::DataGrid.
360
+ lookup_grid_h(pt, 'Gg', { 'i1' => 3, 'i2' => 1 }, false)
361
+ expect(res).to eq(1)
362
+
363
+ res = Marty::DataGrid.
364
+ lookup_grid_h(pt, 'Gg', { 'i1' => 2, 'i2' => 3 }, false)
365
+ expect(res).to eq(20)
366
+ end
367
+
368
+ it 'should handle non-distinct lookups' do
369
+ res = Marty::DataGrid.lookup_grid_h(pt, 'Ge', { 'ltv' => 500 }, false)
370
+
371
+ expect(res).to eq(1.1)
372
+
373
+ expect do
374
+ Marty::DataGrid.lookup_grid_h(pt, 'Ge', { 'ltv' => 500 }, true)
375
+ end.to raise_error(RuntimeError)
376
+ end
377
+
378
+ it 'should handle non-distinct lookups (2)' do
379
+ params = {
380
+ 'client_id' => 700127,
381
+ 'property_state' => 'CA',
382
+ }
383
+ res = Marty::DataGrid.lookup_grid_h(pt, 'Gj', params, false)
384
+
385
+ # should return the upper left corner match
386
+ expect(res).to eq(0.25)
387
+
388
+ expect do
389
+ Marty::DataGrid.lookup_grid_h(pt, 'Gj', params, true)
390
+ end.to raise_error(RuntimeError)
391
+ end
392
+
393
+ it 'should handle boolean lookups' do
394
+ res = [true, false].map do |hb_indicator|
395
+ lookup_grid_helper('infinity',
396
+ 'Gd',
397
+ 'hb_indicator' => hb_indicator,
456
398
  )
457
- expect(res).to eq [nil, 'G1']
458
- end
459
-
460
- it 'should handle string wildcards' do
461
- res = lookup_grid_helper('infinity',
462
- 'G1',
399
+ end
400
+ expect(res).to eq [[456.0, 'Gd'], [123.0, 'Gd']]
401
+ end
402
+
403
+ it 'should handle basic lookups' do
404
+ res = lookup_grid_helper('infinity',
405
+ 'G3',
406
+ 'amount' => 160300,
407
+ 'state' => 'HI',
408
+ )
409
+ expect(res).to eq [1.655, 'G3']
410
+
411
+ [3, 4].each do |units|
412
+ res = lookup_grid_helper('infinity',
413
+ 'G2',
414
+ 'fico' => 720,
415
+ 'units' => units,
416
+ 'ltv' => 100,
417
+ 'cltv' => 110.1,
418
+ )
419
+ expect(res).to eq [5.6, 'G2']
420
+ end
421
+
422
+ dg = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
423
+
424
+ h = {
425
+ 'fico' => 600,
426
+ 'state' => 'RI',
427
+ 'ltv' => 10,
428
+ }
429
+
430
+ res = lookup_grid_helper('infinity', 'G1', h)
431
+ expect(res).to eq [11, 'G1']
432
+
433
+ dg.update_from_import('G1', G1.sub(/11/, '111'))
434
+
435
+ res = lookup_grid_helper('infinity', 'G1', h)
436
+ expect(res).to eq [111, 'G1']
437
+ end
438
+
439
+ it 'should result in error when there are multiple cell hits' do
440
+ expect do
441
+ lookup_grid_helper('infinity',
442
+ 'G2',
463
443
  'fico' => 720,
464
- 'state' => 'GU',
465
- 'ltv' => 80,
444
+ 'ltv' => 100,
445
+ 'cltv' => 110.1,
466
446
  )
467
- expect(res).to eq [22, 'G1']
468
- end
469
-
470
- it 'should handle matches which also have a wildcard match' do
471
- dg_from_import('G9', G9)
472
-
473
- expect do
474
- res = lookup_grid_helper('infinity',
475
- 'G9',
476
- 'state' => 'CA', 'ltv' => 81,
477
- )
478
- end.to raise_error(RuntimeError)
479
-
480
- res = lookup_grid_helper('infinity',
447
+ end.to raise_error(RuntimeError)
448
+ end
449
+
450
+ it 'should return nil when matching data grid cell is nil' do
451
+ res = lookup_grid_helper('infinity',
452
+ 'G1',
453
+ 'fico' => 800,
454
+ 'state' => 'MA',
455
+ 'ltv' => 81,
456
+ )
457
+ expect(res).to eq [nil, 'G1']
458
+ end
459
+
460
+ it 'should handle string wildcards' do
461
+ res = lookup_grid_helper('infinity',
462
+ 'G1',
463
+ 'fico' => 720,
464
+ 'state' => 'GU',
465
+ 'ltv' => 80,
466
+ )
467
+ expect(res).to eq [22, 'G1']
468
+ end
469
+
470
+ it 'should handle matches which also have a wildcard match' do
471
+ dg_from_import('G9', G9)
472
+
473
+ expect do
474
+ res = lookup_grid_helper('infinity',
475
+ 'G9',
476
+ 'state' => 'CA', 'ltv' => 81,
477
+ )
478
+ end.to raise_error(RuntimeError)
479
+
480
+ res = lookup_grid_helper('infinity',
481
+ 'G9',
482
+ 'state' => 'GU', 'ltv' => 81,
483
+ )
484
+ expect(res).to eq [456, 'G9']
485
+ end
486
+
487
+ it 'should raise on nil attr values' do
488
+ dg_from_import('G9', G9)
489
+
490
+ expect do
491
+ lookup_grid_helper('infinity',
481
492
  'G9',
482
- 'state' => 'GU', 'ltv' => 81,
493
+ 'ltv' => 81,
483
494
  )
484
- expect(res).to eq [456, 'G9']
485
- end
495
+ end.to raise_error(/matches > 1/)
486
496
 
487
- it 'should raise on nil attr values' do
488
- dg_from_import('G9', G9)
489
-
490
- expect do
491
- lookup_grid_helper('infinity',
492
- 'G9',
493
- 'ltv' => 81,
494
- )
495
- end .to raise_error(/matches > 1/)
496
-
497
- err = /Data Grid lookup failed/
498
- expect do
499
- lookup_grid_helper('infinity',
500
- 'G9',
501
- { 'state' => 'CA', 'ltv' => nil },
502
- false, false)
503
- end .to raise_error(err)
504
-
505
- res = lookup_grid_helper('infinity',
497
+ err = /Data Grid lookup failed/
498
+ expect do
499
+ lookup_grid_helper('infinity',
506
500
  'G9',
507
- { 'state' => nil, 'ltv' => 81 },
501
+ { 'state' => 'CA', 'ltv' => nil },
508
502
  false, false)
509
-
510
- expect(res).to eq [456, 'G9']
511
- end
512
-
513
- it 'should handle boolean keys' do
514
- res = lookup_grid_helper('infinity',
515
- 'G4',
516
- 'hb_indicator' => true,
517
- 'cltv' => 80,
518
- )
519
- expect(res).to eq [-1.5, 'G4']
520
-
521
- res = lookup_grid_helper('infinity',
522
- 'G4',
523
- 'hb_indicator' => false,
524
- 'cltv' => 80,
525
- )
526
- expect(res).to eq [nil, 'G4']
527
- end
528
-
529
- it 'should handle vertical-only grids' do
530
- res = lookup_grid_helper('infinity',
531
- 'G5',
532
- 'ltv' => 80,
533
- )
534
- expect(res).to eq [-0.375, 'G5']
535
- end
536
-
537
- it 'should handle horiz-only grids' do
538
- res = lookup_grid_helper('infinity',
539
- 'G6',
540
- 'ltv' => 80, 'conforming' => true,
541
- )
542
- expect(res).to eq [-0.375, 'G6']
543
- end
544
-
545
- it 'should handle string typed data grids' do
546
- expect(Marty::DataGrid.lookup('infinity', 'G7').data_type).to eq 'string'
547
-
548
- res = lookup_grid_helper('infinity',
549
- 'G7',
550
- 'hb_indicator' => true,
551
- 'cltv' => 80,
552
- )
553
- expect(res).to eq ['test', 'G7']
554
- end
555
-
556
- it 'should handle DataGrid typed data grids' do
557
- expect(Marty::DataGrid.lookup('infinity', 'G8').data_type).
558
- to eq 'Marty::DataGrid'
559
- g1 = Marty::DataGrid.lookup('infinity', 'G1')
560
-
561
- res = lookup_grid_helper('infinity', 'G8', 'ltv' => 80)
562
- expect(res).to eq [g1, 'G8']
563
- end
564
-
565
- it 'should handle multi DataGrid lookups' do
566
- expect(Marty::DataGrid.lookup('infinity', 'G8').data_type).
567
- to eq 'Marty::DataGrid'
568
-
569
- h = {
570
- 'fico' => 600,
571
- 'state' => 'RI',
572
- 'ltv' => 10,
573
- }
574
-
575
- g1_res = lookup_grid_helper('infinity', 'G1', h)
576
- expect(g1_res).to eq [11, 'G1']
577
-
578
- res = lookup_grid_helper('infinity', 'G8', h, true)
579
-
580
- expect(g1_res).to eq res
581
-
582
- # make sure lookup_grid_h works too
583
- res_h = Marty::DataGrid.lookup_grid_h('infinity', 'G8', h, true)
584
-
585
- expect(g1_res[0]).to eq res_h
586
- end
587
-
588
- it 'should handle DataGrid typed data grids' do
589
- g1 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
590
-
591
- res = lookup_grid_helper('infinity', 'Ga', 'dg' => g1)
592
- expect(res).to eq [7, 'Ga']
593
-
594
- # should be able to lookup bu name as well
595
- res = lookup_grid_helper('infinity', 'Ga', 'dg' => 'G2')
596
-
597
- expect(res).to eq [7, 'Ga']
598
- end
599
-
600
- it 'should handle DataGrid typed data grids -- non mcfly' do
601
- ca = Gemini::State.find_by_name('CA')
602
-
603
- res = lookup_grid_helper('infinity', 'Gb', 'property_state' => ca)
604
- expect(res).to eq [70, 'Gb']
605
-
606
- # should be able to lookup bu name as well
607
- res = lookup_grid_helper('infinity', 'Gb', 'property_state' => 'CA')
608
- expect(res).to eq [70, 'Gb']
609
- end
610
-
611
- it 'should handle typed (enum) data lookup_grid' do
612
- pt = 'infinity'
613
- ca = Gemini::State.find_by_name('CA')
614
-
615
- res = Marty::DataGrid.
616
- lookup_grid_h(pt, 'Gb', { 'property_state' => ca }, false)
617
-
618
- expect(res).to eq 70
619
- end
620
-
621
- it 'should return grid data and metadata simple' do
622
- expected_data = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [1.2, 2.3, 3.4],
623
- [4.5, 5.6, 6.7]]
624
- expected_metadata = [{ 'dir' => 'v',
625
- 'attr' => 'units',
626
- 'keys' => [[1, 2], [1, 2], [3, 4], [3, 4]],
627
- 'type' => 'integer' },
628
- { 'dir' => 'v',
629
- 'attr' => 'ltv',
630
- 'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]'],
631
- 'type' => 'numrange' },
632
- { 'dir' => 'h',
633
- 'attr' => 'cltv',
634
- 'keys' => ['[100,110)', '[110,120)', '[120,]'],
635
- 'type' => 'numrange' },
636
- { 'dir' => 'h',
637
- 'attr' => 'fico',
638
- 'keys' => ['[600,700)', '[700,750)', '[750,]'],
639
- 'type' => 'numrange' }]
640
-
641
- dgh = Marty::DataGrid.lookup_h(pt, 'G2')
642
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt, {}, dgh,
643
- nil, true, true)
644
- expect(res['data']).to eq expected_data
645
- expect(res['metadata']).to eq expected_metadata
646
- end
647
-
648
- it 'should return grid data and metadata multi (following)' do
649
- expected_data = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [1.2, 2.3, 3.4],
650
- [4.5, 5.6, nil], [11.0, 22.0, 33.0]]
651
- expected_metadata = [{ 'dir' => 'v',
652
- 'attr' => 'state',
653
- 'keys' => [['CA'], ['HI', 'TX'], ['NM'], ['MA'], nil],
654
- 'type' => 'string' },
655
- { 'dir' => 'v',
656
- 'attr' => 'ltv',
657
- 'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]',
658
- '[,80]'],
659
- 'type' => 'numrange' },
660
- { 'dir' => 'h',
661
- 'attr' => 'fico',
662
- 'keys' => ['[600,700)', '[700,750)', '[750,]'],
663
- 'type' => 'numrange' }]
664
- dgh = Marty::DataGrid.lookup_h(pt, 'G8')
665
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
666
- { 'ltv' => 10,
667
- 'state' => 'RI' },
668
- dgh, nil, true,
669
- true)
670
- expect(res['data']).to eq expected_data
671
- expect(res['metadata']).to eq expected_metadata
672
- end
673
-
674
- it 'should return grid data and metadata multi (not following)' do
675
- expected_data = [['G1'], ['G2'], ['G3']]
676
- expected_metadata = [{ 'dir' => 'v',
677
- 'attr' => 'ltv',
678
- 'keys' => ['[,115]', '(115,135]', '(135,140]'],
679
- 'type' => 'numrange' }]
680
- dgh = Marty::DataGrid.lookup_h(pt, 'G8')
681
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
682
- { 'ltv' => 10,
683
- 'state' => 'RI' },
684
- dgh, nil, false,
685
- true)
686
- expect(res['data']).to eq expected_data
687
- expect(res['metadata']).to eq expected_metadata
688
- end
689
-
690
- it 'should handle all characters in grid inputs' do
691
- dgh = Marty::DataGrid.lookup_h(pt, 'G1')
692
- 5000.times do
693
- st = 30.times.map { rand(32..255) }.pack('U*')
694
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
695
- { 'ltv' => 10,
696
- 'fico' => 690,
697
- 'state' => st },
698
- dgh, nil, false, true)
503
+ end.to raise_error(err)
504
+
505
+ res = lookup_grid_helper('infinity',
506
+ 'G9',
507
+ { 'state' => nil, 'ltv' => 81 },
508
+ false, false)
509
+
510
+ expect(res).to eq [456, 'G9']
511
+ end
512
+
513
+ it 'should handle boolean keys' do
514
+ res = lookup_grid_helper('infinity',
515
+ 'G4',
516
+ 'hb_indicator' => true,
517
+ 'cltv' => 80,
518
+ )
519
+ expect(res).to eq [-1.5, 'G4']
520
+
521
+ res = lookup_grid_helper('infinity',
522
+ 'G4',
523
+ 'hb_indicator' => false,
524
+ 'cltv' => 80,
525
+ )
526
+ expect(res).to eq [nil, 'G4']
527
+ end
528
+
529
+ it 'should handle vertical-only grids' do
530
+ res = lookup_grid_helper('infinity',
531
+ 'G5',
532
+ 'ltv' => 80,
533
+ )
534
+ expect(res).to eq [-0.375, 'G5']
535
+ end
536
+
537
+ it 'should handle horiz-only grids' do
538
+ res = lookup_grid_helper('infinity',
539
+ 'G6',
540
+ 'ltv' => 80, 'conforming' => true,
541
+ )
542
+ expect(res).to eq [-0.375, 'G6']
543
+ end
544
+
545
+ it 'should handle string typed data grids' do
546
+ expect(Marty::DataGrid.lookup('infinity', 'G7').data_type).to eq 'string'
547
+
548
+ res = lookup_grid_helper('infinity',
549
+ 'G7',
550
+ 'hb_indicator' => true,
551
+ 'cltv' => 80,
552
+ )
553
+ expect(res).to eq ['test', 'G7']
554
+ end
555
+
556
+ it 'should handle DataGrid typed data grids' do
557
+ expect(Marty::DataGrid.lookup('infinity', 'G8').data_type).
558
+ to eq 'Marty::DataGrid'
559
+ g1 = Marty::DataGrid.lookup('infinity', 'G1')
560
+
561
+ res = lookup_grid_helper('infinity', 'G8', 'ltv' => 80)
562
+ expect(res).to eq [g1, 'G8']
563
+ end
564
+
565
+ it 'should handle multi DataGrid lookups' do
566
+ expect(Marty::DataGrid.lookup('infinity', 'G8').data_type).
567
+ to eq 'Marty::DataGrid'
568
+
569
+ h = {
570
+ 'fico' => 600,
571
+ 'state' => 'RI',
572
+ 'ltv' => 10,
573
+ }
574
+
575
+ g1_res = lookup_grid_helper('infinity', 'G1', h)
576
+ expect(g1_res).to eq [11, 'G1']
577
+
578
+ res = lookup_grid_helper('infinity', 'G8', h, true)
579
+
580
+ expect(g1_res).to eq res
581
+
582
+ # make sure lookup_grid_h works too
583
+ res_h = Marty::DataGrid.lookup_grid_h('infinity', 'G8', h, true)
584
+
585
+ expect(g1_res[0]).to eq res_h
586
+ end
587
+
588
+ it 'should handle DataGrid typed data grids' do
589
+ g1 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
590
+
591
+ res = lookup_grid_helper('infinity', 'Ga', 'dg' => g1)
592
+ expect(res).to eq [7, 'Ga']
593
+
594
+ # should be able to lookup bu name as well
595
+ res = lookup_grid_helper('infinity', 'Ga', 'dg' => 'G2')
596
+
597
+ expect(res).to eq [7, 'Ga']
598
+ end
599
+
600
+ it 'should handle DataGrid typed data grids -- non mcfly' do
601
+ ca = Gemini::State.find_by_name('CA')
602
+
603
+ res = lookup_grid_helper('infinity', 'Gb', 'property_state' => ca)
604
+ expect(res).to eq [70, 'Gb']
605
+
606
+ # should be able to lookup bu name as well
607
+ res = lookup_grid_helper('infinity', 'Gb', 'property_state' => 'CA')
608
+ expect(res).to eq [70, 'Gb']
609
+ end
610
+
611
+ it 'should handle typed (enum) data lookup_grid' do
612
+ pt = 'infinity'
613
+ ca = Gemini::State.find_by_name('CA')
614
+
615
+ res = Marty::DataGrid.
616
+ lookup_grid_h(pt, 'Gb', { 'property_state' => ca }, false)
617
+
618
+ expect(res).to eq 70
619
+ end
620
+
621
+ it 'should return grid data and metadata simple' do
622
+ expected_data = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [1.2, 2.3, 3.4],
623
+ [4.5, 5.6, 6.7]]
624
+ expected_metadata = [{ 'dir' => 'v',
625
+ 'attr' => 'units',
626
+ 'keys' => [[1, 2], [1, 2], [3, 4], [3, 4]],
627
+ 'type' => 'integer' },
628
+ { 'dir' => 'v',
629
+ 'attr' => 'ltv',
630
+ 'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]'],
631
+ 'type' => 'numrange' },
632
+ { 'dir' => 'h',
633
+ 'attr' => 'cltv',
634
+ 'keys' => ['[100,110)', '[110,120)', '[120,]'],
635
+ 'type' => 'numrange' },
636
+ { 'dir' => 'h',
637
+ 'attr' => 'fico',
638
+ 'keys' => ['[600,700)', '[700,750)', '[750,]'],
639
+ 'type' => 'numrange' }]
640
+
641
+ dgh = Marty::DataGrid.lookup_h(pt, 'G2')
642
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt, {}, dgh,
643
+ nil, true, true)
644
+ expect(res['data']).to eq expected_data
645
+ expect(res['metadata']).to eq expected_metadata
646
+ end
647
+
648
+ it 'should return grid data and metadata multi (following)' do
649
+ expected_data = [[1.1, 2.2, 3.3], [4.4, 5.5, 6.6], [1.2, 2.3, 3.4],
650
+ [4.5, 5.6, nil], [11.0, 22.0, 33.0]]
651
+ expected_metadata = [{ 'dir' => 'v',
652
+ 'attr' => 'state',
653
+ 'keys' => [['CA'], ['HI', 'TX'], ['NM'], ['MA'], nil],
654
+ 'type' => 'string' },
655
+ { 'dir' => 'v',
656
+ 'attr' => 'ltv',
657
+ 'keys' => ['[,80]', '(80,105]', '[,80]', '(80,105]',
658
+ '[,80]'],
659
+ 'type' => 'numrange' },
660
+ { 'dir' => 'h',
661
+ 'attr' => 'fico',
662
+ 'keys' => ['[600,700)', '[700,750)', '[750,]'],
663
+ 'type' => 'numrange' }]
664
+ dgh = Marty::DataGrid.lookup_h(pt, 'G8')
665
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
666
+ { 'ltv' => 10,
667
+ 'state' => 'RI' },
668
+ dgh, nil, true,
669
+ true)
670
+ expect(res['data']).to eq expected_data
671
+ expect(res['metadata']).to eq expected_metadata
672
+ end
673
+
674
+ it 'should return grid data and metadata multi (not following)' do
675
+ expected_data = [['G1'], ['G2'], ['G3']]
676
+ expected_metadata = [{ 'dir' => 'v',
677
+ 'attr' => 'ltv',
678
+ 'keys' => ['[,115]', '(115,135]', '(135,140]'],
679
+ 'type' => 'numrange' }]
680
+ dgh = Marty::DataGrid.lookup_h(pt, 'G8')
681
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
682
+ { 'ltv' => 10,
683
+ 'state' => 'RI' },
684
+ dgh, nil, false,
685
+ true)
686
+ expect(res['data']).to eq expected_data
687
+ expect(res['metadata']).to eq expected_metadata
688
+ end
689
+
690
+ it 'should handle all characters in grid inputs' do
691
+ dgh = Marty::DataGrid.lookup_h(pt, 'G1')
692
+ 5000.times do
693
+ st = 30.times.map { rand(32..255) }.pack('U*')
694
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(pt,
695
+ { 'ltv' => 10,
696
+ 'fico' => 690,
697
+ 'state' => st },
698
+ dgh, nil, false, true)
699
+ end
700
+ end
701
+ it 'should handle all quote chars in grid inputs' do
702
+ dgh = Marty::DataGrid.lookup_h(pt, 'G1')
703
+ # single, double, backslash, grave, acute, unicode quotes: left single,
704
+ # right single, left double, right double
705
+ quotes = ["'", '"', '\\', '`', "\u00b4", "\u2018", "\u2019",
706
+ "\u201C", "\u201D"]
707
+ 100.times do
708
+ st = 30.times.map { quotes[rand(9)] }.join
709
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(
710
+ pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
711
+ end
712
+ end
713
+ it 'should handle quote chars in object name' do
714
+ dgh = Marty::DataGrid.lookup_h(pt, 'G1')
715
+ st = Gemini::State.new(name: "'\\")
716
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(
717
+ pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
718
+ end
699
719
  end
700
- end
701
- it 'should handle all quote chars in grid inputs' do
702
- dgh = Marty::DataGrid.lookup_h(pt, 'G1')
703
- # single, double, backslash, grave, acute, unicode quotes: left single,
704
- # right single, left double, right double
705
- quotes = ["'", '"', '\\', '`', "\u00b4", "\u2018", "\u2019",
706
- "\u201C", "\u201D"]
707
- 100.times do
708
- st = 30.times.map { quotes[rand(9)] }.join
709
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(
710
- pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
711
- end
712
- end
713
- it 'should handle quote chars in object name' do
714
- dgh = Marty::DataGrid.lookup_h(pt, 'G1')
715
- st = Gemini::State.new(name: "'\\")
716
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(
717
- pt, { 'ltv' => 10, 'fico' => 690, 'state' => st }, dgh, nil, false, true)
718
- end
719
- end
720
-
721
- describe 'exports' do
722
- it 'should export lenient grids correctly' do
723
- dg = dg_from_import('Gf', Gf)
724
- dg2 = dg_from_import('Gf2', dg.export)
725
-
726
- expect(dg.export).to eq(dg2.export)
727
- end
728
- end
729
-
730
- describe 'updates' do
731
- it 'should be possible to modify a grid referenced from a multi-grid' do
732
- dgb = dg_from_import('Gb', Gb, '1/1/2014')
733
- dgc = dg_from_import('Gc', Gc, '2/2/2014')
734
-
735
- dgb.update_from_import('Gb', Gb.sub(/70/, '333'), '1/1/2015')
736
- dgb.update_from_import('Gb', Gb.sub(/70/, '444'), '1/1/2016')
737
720
 
738
- dgch = dgc.attributes.
739
- slice('id', 'group_id', 'created_dt', 'metadata', 'data_type')
740
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(
741
- '2/2/2014', { 'property_state' => 'CA' }, dgch)
742
- expect(res['result']).to eq(70)
721
+ describe 'exports' do
722
+ it 'should export lenient grids correctly' do
723
+ dg = dg_from_import('Gf', Gf)
724
+ dg2 = dg_from_import('Gf2', dg.export)
743
725
 
744
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(
745
- '2/2/2015', { 'property_state' => 'CA' }, dgch)
746
- expect(res['result']).to eq(333)
747
-
748
- res = Marty::DataGrid.lookup_grid_distinct_entry_h(
749
- '2/2/2016', { 'property_state' => 'CA' }, dgch)
750
- expect(res['result']).to eq(444)
751
- end
752
-
753
- it 'should not create a new version if no change has been made' do
754
- dg = dg_from_import('G4', G1)
755
- dg.update_from_import('G4', G1)
756
- expect(Marty::DataGrid.where(group_id: dg.group_id).count).to eq 1
757
- end
758
-
759
- it 'should be able to export and import back grids' do
760
- [G1, G2, G3, G4, G5, G6, G7, G8, G9, Ga, Gb].each_with_index do |grid, i|
761
- dg = dg_from_import("G#{i}", grid)
762
- g1 = dg.export
763
- dg = dg_from_import("Gx#{i}", g1)
764
- g2 = dg.export
765
- expect(g1).to eq g2
726
+ expect(dg.export).to eq(dg2.export)
727
+ end
766
728
  end
767
- end
768
-
769
- it 'should be able to externally export/import grids' do
770
- load_scripts(nil, Date.today)
771
-
772
- dg = dg_from_import('G1', G1)
773
-
774
- p = posting('BASE', DateTime.tomorrow, '?')
775
-
776
- engine = Marty::ScriptSet.new.get_engine('DataReport')
777
- res = engine.evaluate('TableReport',
778
- 'result',
779
- 'pt_name' => p.name,
780
- 'class_name' => 'Marty::DataGrid',
781
- )
782
729
 
783
- # FIXME: really hacky removing "" (data_grid) -- This is a bug
784
- # in TableReport/CSV generation.
785
- res.gsub!(/\"\"/, '')
786
- sum = do_import_summary(Marty::DataGrid,
787
- res,
788
- 'infinity',
789
- nil,
790
- nil,
791
- ',',
792
- )
793
-
794
- expect(sum).to eq(same: 1)
795
-
796
- res11 = res.sub(/G1/, 'G11')
797
-
798
- sum = do_import_summary(
799
- Marty::DataGrid, res11, 'infinity', nil, nil, ',')
800
-
801
- expect(sum).to eq(create: 1)
802
-
803
- g1 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
804
- g11 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G11')
730
+ describe 'updates' do
731
+ it 'should be possible to modify a grid referenced from a multi-grid' do
732
+ dgb = dg_from_import('Gb', Gb, '1/1/2014')
733
+ dgc = dg_from_import('Gc', Gc, '2/2/2014')
734
+
735
+ dgb.update_from_import('Gb', Gb.sub(/70/, '333'), '1/1/2015')
736
+ dgb.update_from_import('Gb', Gb.sub(/70/, '444'), '1/1/2016')
737
+
738
+ dgch = dgc.attributes.
739
+ slice('id', 'group_id', 'created_dt', 'metadata', 'data_type')
740
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(
741
+ '2/2/2014', { 'property_state' => 'CA' }, dgch)
742
+ expect(res['result']).to eq(70)
743
+
744
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(
745
+ '2/2/2015', { 'property_state' => 'CA' }, dgch)
746
+ expect(res['result']).to eq(333)
747
+
748
+ res = Marty::DataGrid.lookup_grid_distinct_entry_h(
749
+ '2/2/2016', { 'property_state' => 'CA' }, dgch)
750
+ expect(res['result']).to eq(444)
751
+ end
752
+
753
+ it 'should not create a new version if no change has been made' do
754
+ dg = dg_from_import('G4', G1)
755
+ dg.update_from_import('G4', G1)
756
+ expect(Marty::DataGrid.where(group_id: dg.group_id).count).to eq 1
757
+ end
758
+
759
+ it 'should be able to export and import back grids' do
760
+ [G1, G2, G3, G4, G5, G6, G7, G8, G9, Ga, Gb].each_with_index do |grid, i|
761
+ dg = dg_from_import("G#{i}", grid)
762
+ g1 = dg.export
763
+ dg = dg_from_import("Gx#{i}", g1)
764
+ g2 = dg.export
765
+ expect(g1).to eq g2
766
+ end
767
+ end
768
+
769
+ it 'should be able to externally export/import grids' do
770
+ load_scripts(nil, Date.today)
771
+
772
+ dg = dg_from_import('G1', G1)
773
+
774
+ p = posting('BASE', DateTime.tomorrow, '?')
775
+
776
+ engine = Marty::ScriptSet.new.get_engine('DataReport')
777
+ res = engine.evaluate('TableReport',
778
+ 'result',
779
+ 'pt_name' => p.name,
780
+ 'class_name' => 'Marty::DataGrid',
781
+ )
782
+
783
+ # FIXME: really hacky removing "" (data_grid) -- This is a bug
784
+ # in TableReport/CSV generation.
785
+ res.gsub!(/\"\"/, '')
786
+ sum = do_import_summary(Marty::DataGrid,
787
+ res,
788
+ 'infinity',
789
+ nil,
790
+ nil,
791
+ ',',
792
+ )
793
+
794
+ expect(sum).to eq(same: 1)
795
+
796
+ res11 = res.sub(/G1/, 'G11')
797
+
798
+ sum = do_import_summary(
799
+ Marty::DataGrid, res11, 'infinity', nil, nil, ',')
800
+
801
+ expect(sum).to eq(create: 1)
802
+
803
+ g1 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G1')
804
+ g11 = Marty::DataGrid.find_by(obsoleted_dt: 'infinity', name: 'G11')
805
+
806
+ expect(g1.export).to eq g11.export
807
+ end
808
+ end
805
809
 
806
- expect(g1.export).to eq g11.export
807
- end
808
- end
810
+ # write a grid of varying type and leniency; also allow implicit
811
+ # or explicit declaration of type (for float which is the default)
812
+ def type_grid(lenient, type, constraint, values3, explicit_float: false)
813
+ lenient_str = lenient ? 'lenient' : nil
814
+ # rubocop:disable Style/NestedTernaryOperator
815
+ type_str = type == 'float' ? (explicit_float ? 'float' : nil) : type
816
+ # rubocop:enable Style/NestedTernaryOperator
817
+ top = [lenient_str, type_str].compact.join(' ') + "\t" + constraint + "\n"
818
+ (top =~ /\A\s*\z/ ? '' : top) +
819
+ <<~EOS
820
+ b\tboolean\tv
821
+ i\tinteger\tv
822
+ i4\tint4range\tv
823
+ n\tnumrange\tv
824
+
825
+ true\t1\t<10\t<10.0\t#{values3[0]}
826
+ \t2\t\t\t#{values3[1]}
827
+ false\t\t>10\t\t#{values3[2]}
828
+ EOS
829
+ end
830
+ describe 'constraint' do
831
+ it 'constraint' do
832
+ Mcfly.whodunnit = system_user
833
+ Gemini::BudCategory.create!(name: 'cat1')
834
+ Gemini::BudCategory.create!(name: 'cat2')
835
+ Gemini::BudCategory.create!(name: 'cat3')
836
+ tests = JSON.parse(File.read('spec/fixtures/json/data_grid.json'))
837
+ aggregate_failures do
838
+ tests.each do |test|
839
+ keys = %w[id type constraint values error]
840
+ id, type, constraint, values, error = test.values_at(*keys)
841
+ err_re = Regexp.new(error) if error
842
+ # for float, do both ex- and implicit declaration
843
+ exfls = type == 'float' ? [true, false] : [true]
844
+ [true, false].each do |lenient|
845
+ exfls.each do |exfl|
846
+ grid = type_grid(lenient, type, constraint, values,
847
+ explicit_float: exfl)
848
+ got = nil
849
+ tnam = "Test #{id} lenient=#{lenient} exfl=#{exfl}"
850
+ begin
851
+ dg_from_import(tnam, grid)
852
+ rescue StandardError => e
853
+ got = e.message
854
+ end
855
+ ne = 'no error'
856
+ if error
857
+ # rubocop:disable Lint/Debugger
858
+ binding.pry if ENV['PRY'] && !err_re.match(got)
859
+ expect(got).to match(err_re), tnam + ' failed: got ' +
860
+ got || ne
861
+ else
862
+ binding.pry if ENV['PRY'] && got
863
+ # rubocop:enable Lint/Debugger
864
+ expect(got).to be_nil, tnam + ' failed: got ' + (got || '')
865
+ end
866
+ end
867
+ end
868
+ end
869
+ end
870
+ end
871
+ end
809
872
  end
810
873
  end