forest_liana 7.0.0.beta.4 → 7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +2 -2
  3. data/app/controllers/forest_liana/associations_controller.rb +2 -2
  4. data/app/controllers/forest_liana/resources_controller.rb +15 -6
  5. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  6. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -3
  7. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  8. data/app/services/forest_liana/filters_parser.rb +25 -9
  9. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  10. data/app/services/forest_liana/has_many_getter.rb +2 -2
  11. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  12. data/app/services/forest_liana/line_stat_getter.rb +4 -2
  13. data/app/services/forest_liana/permissions_checker.rb +3 -4
  14. data/app/services/forest_liana/permissions_getter.rb +2 -2
  15. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  16. data/app/services/forest_liana/resource_getter.rb +6 -3
  17. data/app/services/forest_liana/resource_updater.rb +5 -2
  18. data/app/services/forest_liana/resources_getter.rb +6 -5
  19. data/app/services/forest_liana/scope_manager.rb +102 -0
  20. data/app/services/forest_liana/search_query_builder.rb +6 -3
  21. data/app/services/forest_liana/stat_getter.rb +2 -1
  22. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  23. data/config/routes.rb +3 -0
  24. data/lib/forest_liana/version.rb +1 -1
  25. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  26. data/spec/dummy/config/routes.rb +4 -0
  27. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  28. data/spec/requests/actions_controller_spec.rb +100 -12
  29. data/spec/requests/resources_spec.rb +2 -0
  30. data/spec/services/forest_liana/filters_parser_spec.rb +27 -1
  31. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  32. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  33. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +1 -3
  34. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  35. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  36. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  37. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  38. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  39. metadata +20 -15
  40. data/app/services/forest_liana/scope_validator.rb +0 -98
  41. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  42. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  43. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  44. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  45. data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
@@ -17,6 +17,8 @@ describe 'Requesting Tree resources', :type => :request do
17
17
  allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
18
18
 
19
19
  allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { true }
20
+
21
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return({})
20
22
  end
21
23
 
22
24
  token = JWT.encode({
@@ -11,6 +11,7 @@ module ForestLiana
11
11
  let(:date_condition_1) { { 'field' => 'created_at', 'operator' => 'before', 'value' => 2.hours.ago } }
12
12
  let(:date_condition_2) { { 'field' => 'created_at', 'operator' => 'today' } }
13
13
  let(:date_condition_3) { { 'field' => 'created_at', 'operator' => 'previous_x_days', 'value' => 2 } }
14
+ let(:presence_condition) { { 'field' => 'name', 'operator' => 'present' } }
14
15
 
15
16
  before {
16
17
  island = Island.create!(name: "L'île de la muerta")
@@ -274,6 +275,11 @@ module ForestLiana
274
275
  let(:filters) { { 'aggregator' => 'or', 'conditions' => [simple_condition_2, simple_condition_3] } }
275
276
  it { expect(resource.where(query).count).to eq 2 }
276
277
  end
278
+
279
+ context "'name ends_with \"3\"' 'or' 'name is not null'" do
280
+ let(:filters) { { 'aggregator' => 'or', 'conditions' => [simple_condition_2, presence_condition] } }
281
+ it { expect(resource.where(query).count).to eq 3 }
282
+ end
277
283
  end
278
284
 
279
285
  describe 'parse_condition' do
@@ -281,7 +287,27 @@ module ForestLiana
281
287
  let(:result) { filter_parser.parse_condition(condition) }
282
288
 
283
289
  context 'on valid condition' do
284
- it { expect(result).to eq "\"trees\".\"name\" LIKE '%3'" }
290
+ context 'when the condition uses the contains operator' do
291
+ it { expect(result).to eq "\"trees\".\"name\" LIKE '%3'" }
292
+ end
293
+
294
+ context 'when the condition uses the blank operator' do
295
+ let(:condition) { { 'field' => 'name', 'operator' => 'blank' } }
296
+
297
+ it { expect(result).to eq "\"trees\".\"name\" IS NULL" }
298
+ end
299
+
300
+ context 'when the condition uses the presence operator' do
301
+ let(:condition) { presence_condition }
302
+
303
+ it { expect(result).to eq "\"trees\".\"name\" IS NOT NULL" }
304
+ end
305
+
306
+ context 'when the condition uses the in operator' do
307
+ let(:condition) { { 'field' => 'name', 'operator' => 'in', 'value' => ['Tree n1', 'Tree n3'] } }
308
+
309
+ it { expect(result).to eq "\"trees\".\"name\" IN ('Tree n1','Tree n3')" }
310
+ end
285
311
  end
286
312
 
287
313
  context 'on belongs_to condition' do
@@ -0,0 +1,116 @@
1
+ module ForestLiana
2
+ describe HasManyGetter do
3
+ describe 'when retrieving has many relationship related records' do
4
+ let(:rendering_id) { 13 }
5
+ let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
6
+ let(:scopes) { { } }
7
+ let(:association) { Island.reflect_on_association(:trees) }
8
+ let(:params) {
9
+ {
10
+ id: Island.first.id,
11
+ association_name: 'trees',
12
+ page: { size: 15, number: 1 },
13
+ timezone: 'America/Nome'
14
+ }
15
+ }
16
+
17
+ subject {
18
+ described_class.new(Island, association, params, user)
19
+ }
20
+
21
+ before(:each) do
22
+ madagascar = Island.create(name: 'madagascar')
23
+ re = Island.create(name: 'ré')
24
+ Tree.create(name: 'lemon tree', island: madagascar)
25
+ Tree.create(name: 'banana tree', island: madagascar)
26
+ Tree.create(name: 'papaya tree', island: madagascar)
27
+ Tree.create(name: 'apple tree', island: re)
28
+ Tree.create(name: 'banana tree', island: re)
29
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
30
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
31
+ end
32
+
33
+ after(:each) do
34
+ Island.destroy_all
35
+ Tree.destroy_all
36
+ end
37
+
38
+ describe 'with empty scopes' do
39
+ describe 'with page 1 size 15' do
40
+ it 'should return the 3 trees matching madagascar' do
41
+ subject.perform
42
+
43
+ expect(subject.records.count).to eq 3
44
+ expect(subject.count).to eq 3
45
+ end
46
+ end
47
+
48
+ describe 'when sorting by decreasing id' do
49
+ let(:params) {
50
+ {
51
+ id: Island.first.id,
52
+ association_name: 'trees',
53
+ sort: '-id',
54
+ page: { size: 15, number: 1 },
55
+ timezone: 'America/Nome'
56
+ }
57
+ }
58
+
59
+ it 'should order records properly' do
60
+ subject.perform
61
+
62
+ expect(subject.records.count).to eq 3
63
+ expect(subject.count).to eq 3
64
+ expect(subject.records.first.id).to be > subject.records.last.id
65
+ end
66
+ end
67
+
68
+ describe 'when searching for banana tree' do
69
+ let(:params) {
70
+ {
71
+ id: Island.first.id,
72
+ association_name: 'trees',
73
+ search: 'banana',
74
+ page: { size: 15, number: 1 },
75
+ timezone: 'America/Nome'
76
+ }
77
+ }
78
+
79
+ it 'should return only the banana tree linked to madagascar' do
80
+ subject.perform
81
+
82
+ expect(subject.records.count).to eq 1
83
+ expect(subject.count).to eq 1
84
+ expect(subject.records.first.island.name).to eq 'madagascar'
85
+ end
86
+ end
87
+ end
88
+
89
+ describe 'with scopes' do
90
+ let(:scopes) { {
91
+ 'Tree' => {
92
+ 'scope'=> {
93
+ 'filter'=> {
94
+ 'aggregator' => 'and',
95
+ 'conditions' => [
96
+ { 'field' => 'name', 'operator' => 'contains', 'value' => 'a' }
97
+ ]
98
+ },
99
+ 'dynamicScopesValues' => { }
100
+ }
101
+ }
102
+ } }
103
+
104
+ describe 'when asking for all trees related to madagascar' do
105
+ it 'should return trees belonging to madagascar and matching the scopes' do
106
+ subject.perform
107
+
108
+ # Only `papaya` and `banana` contain an `a`
109
+ expect(subject.records.count).to eq 2
110
+ expect(subject.count).to eq 2
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -1,12 +1,21 @@
1
1
  module ForestLiana
2
2
  describe LineStatGetter do
3
+ let(:rendering_id) { 13 }
4
+ let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
5
+ let(:scopes) { { } }
6
+
7
+ before(:each) do
8
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
9
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
10
+ end
11
+
3
12
  describe 'Check client_timezone function' do
4
13
  describe 'with a SQLite database' do
5
14
  it 'should return false' do
6
15
  expect(LineStatGetter.new(Owner, {
7
16
  timezone: "Europe/Paris",
8
17
  aggregate: "Count",
9
- }).client_timezone).to eq(false)
18
+ }, user).client_timezone).to eq(false)
10
19
  end
11
20
  end
12
21
 
@@ -16,7 +25,7 @@ module ForestLiana
16
25
  expect(LineStatGetter.new(Owner, {
17
26
  timezone: "Europe/Paris",
18
27
  aggregate: "Count",
19
- }).client_timezone).to eq('Europe/Paris')
28
+ }, user).client_timezone).to eq('Europe/Paris')
20
29
  end
21
30
  end
22
31
  end
@@ -25,7 +34,6 @@ module ForestLiana
25
34
  describe 'Using a Count aggregation' do
26
35
  describe 'Using a Week time range' do
27
36
  it 'should return consistent data based on monday as week_start ' do
28
-
29
37
  # Week should start on monday
30
38
  # 08-05-2021 was a Saturday
31
39
  Owner.create(name: 'Michel', hired_at: Date.parse('08-05-2021'));
@@ -38,8 +46,8 @@ module ForestLiana
38
46
  aggregate: "Count",
39
47
  time_range: "Week",
40
48
  group_by_date_field: "hired_at",
41
- }).perform
42
-
49
+ }, user).perform
50
+
43
51
  expect(stat.value.find { |item| item[:label] == "W18-2021" }[:values][:value]).to eq(2)
44
52
  expect(stat.value.find { |item| item[:label] == "W19-2021" }[:values][:value]).to eq(2)
45
53
  end
@@ -47,4 +55,4 @@ module ForestLiana
47
55
  end
48
56
  end
49
57
  end
50
- end
58
+ end
@@ -191,7 +191,6 @@ module ForestLiana
191
191
  end
192
192
  end
193
193
 
194
-
195
194
  context 'renderings cache' do
196
195
  let(:fake_ressource) { collection_name }
197
196
  let(:rendering_id) { 1 }
@@ -499,7 +498,7 @@ module ForestLiana
499
498
  expect(subject.is_authorized?).to be false
500
499
  end
501
500
  end
502
-
501
+
503
502
  end
504
503
 
505
504
  context 'when user has not the required permission' do
@@ -519,7 +518,6 @@ module ForestLiana
519
518
  expect(subject.is_authorized?).to be false
520
519
  end
521
520
  end
522
-
523
521
  end
524
522
  end
525
523
 
@@ -0,0 +1,114 @@
1
+ module ForestLiana
2
+ describe PieStatGetter do
3
+ let(:rendering_id) { 13 }
4
+ let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
5
+ let(:records) { [
6
+ { name: 'Young Tree n1', age: 3 },
7
+ { name: 'Young Tree n2', age: 3 },
8
+ { name: 'Young Tree n3', age: 3 },
9
+ { name: 'Young Tree n4', age: 3 },
10
+ { name: 'Young Tree n5', age: 3 },
11
+ { name: 'Old Tree n1', age: 15 },
12
+ { name: 'Old Tree n2', age: 15 },
13
+ { name: 'Old Tree n3', age: 15 },
14
+ { name: 'Old Tree n4', age: 15 }
15
+ ] }
16
+
17
+ before(:each) do
18
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
19
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
20
+
21
+ records.each { |record|
22
+ Tree.create!(name: record[:name], age: record[:age])
23
+ }
24
+ end
25
+
26
+ let(:model) { Tree }
27
+ let(:collection) { 'trees' }
28
+ let(:params) {
29
+ {
30
+ type: 'Pie',
31
+ collection: collection,
32
+ timezone: 'Europe/Paris',
33
+ aggregate: 'Count',
34
+ group_by_field: group_by_field
35
+ }
36
+ }
37
+
38
+ subject { PieStatGetter.new(model, params, user) }
39
+
40
+ describe 'with empty scopes' do
41
+ let(:scopes) { { } }
42
+
43
+ describe 'with an aggregate on the name field' do
44
+ let(:group_by_field) { 'name' }
45
+
46
+ it 'should be as many categories as records count' do
47
+ subject.perform
48
+ expect(subject.record.value).to eq [
49
+ {:key => "Old Tree n1", :value => 1},
50
+ {:key => "Old Tree n2", :value => 1},
51
+ {:key => "Old Tree n3", :value => 1},
52
+ {:key => "Old Tree n4", :value => 1},
53
+ {:key => "Young Tree n1", :value => 1},
54
+ {:key => "Young Tree n2", :value => 1},
55
+ {:key => "Young Tree n3", :value => 1},
56
+ {:key => "Young Tree n4", :value => 1},
57
+ {:key => "Young Tree n5", :value => 1}
58
+ ]
59
+ end
60
+ end
61
+
62
+ describe 'with an aggregate on the age field' do
63
+ let(:group_by_field) { 'age' }
64
+
65
+ it 'should be as many categories as different ages among records' do
66
+ subject.perform
67
+ expect(subject.record.value).to eq [{ :key => 3, :value => 5}, { :key => 15, :value => 4 }]
68
+ end
69
+ end
70
+ end
71
+
72
+ describe 'with scopes' do
73
+ let(:scopes) {
74
+ {
75
+ 'Tree' => {
76
+ 'scope'=> {
77
+ 'filter'=> {
78
+ 'aggregator' => 'and',
79
+ 'conditions' => [
80
+ { 'field' => 'age', 'operator' => 'less_than', 'value' => 10 }
81
+ ]
82
+ },
83
+ 'dynamicScopesValues' => { }
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ describe 'with an aggregate on the name field' do
90
+ let(:group_by_field) { 'name' }
91
+
92
+ it 'should be as many categories as records inside the scope' do
93
+ subject.perform
94
+ expect(subject.record.value).to eq [
95
+ {:key => "Young Tree n1", :value => 1},
96
+ {:key => "Young Tree n2", :value => 1},
97
+ {:key => "Young Tree n3", :value => 1},
98
+ {:key => "Young Tree n4", :value => 1},
99
+ {:key => "Young Tree n5", :value => 1}
100
+ ]
101
+ end
102
+ end
103
+
104
+ describe 'with an aggregate on the age field' do
105
+ let(:group_by_field) { 'age' }
106
+
107
+ it 'should be only one category' do
108
+ subject.perform
109
+ expect(subject.record.value).to eq [{ :key => 3, :value => 5}]
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,116 @@
1
+ module ForestLiana
2
+ describe ResourceUpdater do
3
+ describe 'when updating a record' do
4
+ let(:params) {
5
+ ActionController::Parameters.new(
6
+ id: 1,
7
+ data: {
8
+ id: 1,
9
+ type: 'User',
10
+ attributes: attributes
11
+ }
12
+ )
13
+ }
14
+ let(:rendering_id) { 13 }
15
+ let(:user) { { 'id' => '1', 'rendering_id' => rendering_id } }
16
+ let(:scopes) { { } }
17
+
18
+ subject {
19
+ described_class.new(User, params, user)
20
+ }
21
+
22
+ before(:each) do
23
+ User.create(name: 'Merry')
24
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
25
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(scopes)
26
+ end
27
+
28
+ after(:each) do
29
+ User.destroy_all
30
+ end
31
+
32
+ describe 'with empty scopes' do
33
+ describe 'with a missing name in attributes' do
34
+ let(:attributes) { { } }
35
+
36
+ it 'should not update the record name' do
37
+ subject.perform
38
+
39
+ expect(subject.record.valid?).to be true
40
+ expect(subject.record.name).to eq 'Merry'
41
+ end
42
+ end
43
+
44
+ describe 'with a null name in attributes' do
45
+ let(:attributes) { { name: nil } }
46
+
47
+ it 'should set the record name to null' do
48
+ subject.perform
49
+
50
+ expect(subject.record.valid?).to be true
51
+ expect(subject.record.name).to eq nil
52
+ end
53
+ end
54
+
55
+ describe 'with a new value as name in attributes' do
56
+ let(:attributes) { { name: 'Pippin' } }
57
+
58
+ it 'should set the record name to null' do
59
+ subject.perform
60
+
61
+ expect(subject.record.valid?).to be true
62
+ expect(subject.record.name).to eq 'Pippin'
63
+ end
64
+ end
65
+ end
66
+
67
+ describe 'with scope excluding target record' do
68
+ let(:attributes) { { name: 'Gandalf' } }
69
+ let(:scopes) { {
70
+ 'User' => {
71
+ 'scope'=> {
72
+ 'filter'=> {
73
+ 'aggregator' => 'and',
74
+ 'conditions' => [
75
+ { 'field' => 'id', 'operator' => 'greater_than', 'value' => 2 }
76
+ ]
77
+ },
78
+ 'dynamicScopesValues' => { }
79
+ }
80
+ }
81
+ } }
82
+
83
+ it 'should not update the record name' do
84
+ subject.perform
85
+
86
+ expect(subject.record).to be nil
87
+ expect(subject.errors[0][:detail]).to eq 'Couldn\'t find User with \'id\'=1 [WHERE (("users"."id" > 2))]'
88
+ end
89
+ end
90
+
91
+ describe 'with scope including target record' do
92
+ let(:attributes) { { name: 'Gandalf' } }
93
+ let(:scopes) { {
94
+ 'User' => {
95
+ 'scope'=> {
96
+ 'filter'=> {
97
+ 'aggregator' => 'and',
98
+ 'conditions' => [
99
+ { 'field' => 'id', 'operator' => 'less_than', 'value' => 2 }
100
+ ]
101
+ },
102
+ 'dynamicScopesValues' => { }
103
+ }
104
+ }
105
+ } }
106
+
107
+ it 'should not update the record name' do
108
+ subject.perform
109
+
110
+ expect(subject.record.valid?).to be true
111
+ expect(subject.record.name).to eq 'Gandalf'
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end