marty 2.9.3 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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