forest_liana 7.0.0.beta.2 → 7.0.0.beta.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/forest_liana/actions_controller.rb +20 -18
  3. data/app/controllers/forest_liana/application_controller.rb +0 -9
  4. data/app/controllers/forest_liana/associations_controller.rb +2 -2
  5. data/app/controllers/forest_liana/resources_controller.rb +16 -6
  6. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  7. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -6
  8. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  9. data/app/services/forest_liana/apimap_sorter.rb +1 -0
  10. data/app/services/forest_liana/filters_parser.rb +8 -4
  11. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  12. data/app/services/forest_liana/has_many_getter.rb +2 -2
  13. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  14. data/app/services/forest_liana/line_stat_getter.rb +5 -3
  15. data/app/services/forest_liana/permissions_checker.rb +42 -37
  16. data/app/services/forest_liana/permissions_formatter.rb +1 -1
  17. data/app/services/forest_liana/permissions_getter.rb +3 -6
  18. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  19. data/app/services/forest_liana/resource_getter.rb +6 -3
  20. data/app/services/forest_liana/resource_updater.rb +5 -2
  21. data/app/services/forest_liana/resources_getter.rb +6 -5
  22. data/app/services/forest_liana/scope_manager.rb +102 -0
  23. data/app/services/forest_liana/search_query_builder.rb +6 -3
  24. data/app/services/forest_liana/stat_getter.rb +2 -1
  25. data/app/services/forest_liana/token.rb +1 -0
  26. data/app/services/forest_liana/utils/beta_schema_utils.rb +1 -1
  27. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  28. data/config/routes.rb +3 -1
  29. data/lib/forest_liana/bootstrapper.rb +4 -2
  30. data/lib/forest_liana/version.rb +1 -1
  31. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  32. data/spec/dummy/config/routes.rb +4 -0
  33. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  34. data/spec/lib/forest_liana/bootstrapper_spec.rb +12 -0
  35. data/spec/requests/actions_controller_spec.rb +144 -23
  36. data/spec/requests/authentications_spec.rb +2 -1
  37. data/spec/requests/resources_spec.rb +2 -0
  38. data/spec/services/forest_liana/apimap_sorter_spec.rb +6 -4
  39. data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
  40. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  41. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  42. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +45 -71
  43. data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +39 -63
  44. data/spec/services/forest_liana/permissions_checker_live_queries_spec.rb +3 -3
  45. data/spec/services/forest_liana/permissions_formatter_spec.rb +11 -11
  46. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  47. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  48. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  49. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  50. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  51. metadata +125 -118
  52. data/app/services/forest_liana/scope_validator.rb +0 -98
  53. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  54. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  55. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  56. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  57. data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
@@ -2,9 +2,10 @@ module ForestLiana
2
2
  class StatGetter < BaseGetter
3
3
  attr_accessor :record
4
4
 
5
- def initialize(resource, params)
5
+ def initialize(resource, params, forest_user)
6
6
  @resource = resource
7
7
  @params = params
8
+ @user = forest_user
8
9
  compute_includes()
9
10
  end
10
11
  end
@@ -19,6 +19,7 @@ module ForestLiana
19
19
  first_name: user['first_name'],
20
20
  last_name: user['last_name'],
21
21
  team: user['teams'][0],
22
+ role: user['role'],
22
23
  rendering_id: rendering_id,
23
24
  exp: expiration_in_seconds()
24
25
  }, ForestLiana.auth_secret, 'HS256')
@@ -6,7 +6,7 @@ module ForestLiana
6
6
 
7
7
  return nil unless collection
8
8
 
9
- collection.actions.find { |action| action.endpoint == endpoint && action.http_method == http_method }
9
+ collection.actions.find { |action| (action.endpoint == endpoint || "/#{action.endpoint}" == endpoint) && action.http_method == http_method }
10
10
  end
11
11
  end
12
12
  end
@@ -6,8 +6,10 @@ module ForestLiana
6
6
  return if @params[:aggregate].blank?
7
7
  resource = get_resource().eager_load(@includes)
8
8
 
9
- unless @params[:filters].blank?
10
- filter_parser = FiltersParser.new(@params[:filters], resource, @params[:timezone])
9
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @resource.name)
10
+
11
+ unless filters.blank?
12
+ filter_parser = FiltersParser.new(filters, resource, @params[:timezone])
11
13
  resource = filter_parser.apply_filters
12
14
  raw_previous_interval = filter_parser.get_previous_interval_condition
13
15
 
data/config/routes.rb CHANGED
@@ -20,6 +20,9 @@ ForestLiana::Engine.routes.draw do
20
20
  post '/stats/:collection' => 'stats#get'
21
21
  post '/stats' => 'stats#get_with_live_query'
22
22
 
23
+ # Scopes
24
+ post '/scope-cache-invalidation' => 'scopes#invalidate_scope_cache'
25
+
23
26
  # Stripe Integration
24
27
  get '(*collection)_stripe_payments' => 'stripe#payments'
25
28
  get ':collection/:id/stripe_payments' => 'stripe#payments'
@@ -57,7 +60,6 @@ ForestLiana::Engine.routes.draw do
57
60
  delete ':collection', to: router
58
61
 
59
62
  # Smart Actions forms value
60
- post 'actions/:action_name/values' => 'actions#values'
61
63
  post 'actions/:action_name/hooks/load' => 'actions#load'
62
64
  post 'actions/:action_name/hooks/change' => 'actions#change'
63
65
  end
@@ -203,10 +203,12 @@ module ForestLiana
203
203
 
204
204
  def setup_forest_liana_meta
205
205
  ForestLiana.meta = {
206
- database_type: database_type,
207
206
  liana: 'forest-rails',
208
207
  liana_version: ForestLiana::VERSION,
209
- orm_version: Gem.loaded_specs["activerecord"].version.version
208
+ stack: {
209
+ database_type: database_type,
210
+ orm_version: Gem.loaded_specs["activerecord"].version.version,
211
+ }
210
212
  }
211
213
  end
212
214
 
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "7.0.0.beta.2"
2
+ VERSION = "7.0.0.beta.6"
3
3
  end
@@ -0,0 +1,5 @@
1
+ class Forest::IslandsController < ForestLiana::SmartActionsController
2
+ def test
3
+ render json: { success: 'You are OK.' }
4
+ end
5
+ end
@@ -1,3 +1,7 @@
1
1
  Rails.application.routes.draw do
2
+ namespace :forest do
3
+ post '/actions/test' => 'islands#test'
4
+ end
5
+
2
6
  mount ForestLiana::Engine => "/forest"
3
7
  end
@@ -0,0 +1,7 @@
1
+ class Forest::Island
2
+ include ForestLiana::Collection
3
+
4
+ collection :Island
5
+
6
+ action 'Test'
7
+ end
@@ -0,0 +1,12 @@
1
+ module ForestLiana
2
+ describe Bootstrapper do
3
+ describe 'setup_forest_liana_meta' do
4
+ it "should put statistic data related to user stack on a dedicated object" do
5
+ expect(ForestLiana.meta[:stack])
6
+ .to include(:orm_version)
7
+ expect(ForestLiana.meta[:stack])
8
+ .to include(:database_type)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,23 +1,41 @@
1
1
  require 'rails_helper'
2
2
 
3
3
  describe 'Requesting Actions routes', :type => :request do
4
+ let(:rendering_id) { 13 }
5
+ let(:scope_filters) { nil }
6
+
4
7
  before(:each) do
5
8
  allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
6
9
  allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
7
- Island.create(name: 'Corsica')
10
+ Island.create(id: 1, name: 'Corsica')
11
+
12
+ ForestLiana::ScopeManager.invalidate_scope_cache(rendering_id)
13
+ allow(ForestLiana::ScopeManager).to receive(:get_scope_for_user).and_return(scope_filters)
8
14
  end
9
15
 
10
16
  after(:each) do
11
17
  Island.destroy_all
12
18
  end
13
19
 
14
- describe 'call /values' do
15
- it 'should respond 200' do
16
- post '/forest/actions/foo/values', {}
17
- expect(response.status).to eq(200)
18
- expect(response.body).to be {}
19
- end
20
- end
20
+ let(:token) {
21
+ JWT.encode({
22
+ id: 38,
23
+ email: 'michael.kelso@that70.show',
24
+ first_name: 'Michael',
25
+ last_name: 'Kelso',
26
+ team: 'Operations',
27
+ rendering_id: rendering_id,
28
+ exp: Time.now.to_i + 2.weeks.to_i
29
+ }, ForestLiana.auth_secret, 'HS256')
30
+ }
31
+
32
+ let(:headers) {
33
+ {
34
+ 'Accept' => 'application/json',
35
+ 'Content-Type' => 'application/json',
36
+ 'Authorization' => "Bearer #{token}"
37
+ }
38
+ }
21
39
 
22
40
  describe 'hooks' do
23
41
  foo = {
@@ -127,28 +145,32 @@ describe 'Requesting Actions routes', :type => :request do
127
145
  island.actions = [action, fail_action, cheat_action, enums_action, multiple_enums_action]
128
146
 
129
147
  describe 'call /load' do
130
- params = {recordIds: [1], collectionName: 'Island'}
148
+ params = {
149
+ data: {
150
+ attributes: { ids: [1], collection_name: 'Island' }
151
+ }
152
+ }
131
153
 
132
154
  it 'should respond 200' do
133
- post '/forest/actions/my_action/hooks/load', params: JSON.dump(params), headers: { 'CONTENT_TYPE' => 'application/json' }
155
+ post '/forest/actions/my_action/hooks/load', params: JSON.dump(params), headers: headers
134
156
  expect(response.status).to eq(200)
135
157
  expect(JSON.parse(response.body)).to eq({'fields' => [foo.merge({:value => nil}).stringify_keys]})
136
158
  end
137
159
 
138
160
  it 'should respond 500 with bad params' do
139
- post '/forest/actions/my_action/hooks/load', params: {}
161
+ post '/forest/actions/my_action/hooks/load', params: {}, headers: headers
140
162
  expect(response.status).to eq(500)
141
163
  expect(JSON.parse(response.body)).to eq({'error' => 'Error in smart action load hook: cannot retrieve action from collection'})
142
164
  end
143
165
 
144
166
  it 'should respond 500 with bad hook result type' do
145
- post '/forest/actions/fail_action/hooks/load', params: JSON.dump(params), headers: { 'CONTENT_TYPE' => 'application/json' }
167
+ post '/forest/actions/fail_action/hooks/load', params: JSON.dump(params), headers: headers
146
168
  expect(response.status).to eq(500)
147
169
  expect(JSON.parse(response.body)).to eq({'error' => 'Error in smart action load hook: hook must return an array of fields'})
148
170
  end
149
171
 
150
172
  it 'should respond 500 with bad hook result data structure' do
151
- post '/forest/actions/cheat_action/hooks/load', params: JSON.dump(params), headers: { 'CONTENT_TYPE' => 'application/json' }
173
+ post '/forest/actions/cheat_action/hooks/load', params: JSON.dump(params), headers: headers
152
174
  expect(response.status).to eq(500)
153
175
  expect(JSON.parse(response.body)).to eq({'error' => 'Error in smart action load hook: hook must return an array of fields'})
154
176
  end
@@ -156,10 +178,19 @@ describe 'Requesting Actions routes', :type => :request do
156
178
 
157
179
  describe 'call /change' do
158
180
  updated_foo = foo.clone.merge({:previousValue => nil, :value => 'bar'})
159
- params = {recordIds: [1], fields: [updated_foo], collectionName: 'Island', changedField: 'foo'}
181
+ params = {
182
+ data: {
183
+ attributes: {
184
+ ids: [1],
185
+ fields: [updated_foo],
186
+ collection_name: 'Island',
187
+ changed_field: 'foo'
188
+ }
189
+ }
190
+ }
160
191
 
161
192
  it 'should respond 200' do
162
- post '/forest/actions/my_action/hooks/change', params: JSON.dump(params), headers: { 'CONTENT_TYPE' => 'application/json' }
193
+ post '/forest/actions/my_action/hooks/change', params: JSON.dump(params), headers: headers
163
194
  expect(response.status).to eq(200)
164
195
  expected = updated_foo.clone.merge({:value => 'baz'})
165
196
  expected[:widgetEdit] = nil
@@ -168,21 +199,30 @@ describe 'Requesting Actions routes', :type => :request do
168
199
  end
169
200
 
170
201
  it 'should respond 500 with bad params' do
171
- post '/forest/actions/my_action/hooks/change', params: JSON.dump({collectionName: 'Island'}), headers: { 'CONTENT_TYPE' => 'application/json' }
202
+ post '/forest/actions/my_action/hooks/change', params: JSON.dump({ data: { attributes: { collection_name: 'Island' }}}), headers: headers
172
203
  expect(response.status).to eq(500)
173
204
  expect(JSON.parse(response.body)).to eq({'error' => 'Error in smart action change hook: fields params is mandatory'})
174
205
  end
175
206
 
176
207
  it 'should respond 500 with bad hook result type' do
177
- post '/forest/actions/fail_action/hooks/change', params: JSON.dump(params), headers: { 'CONTENT_TYPE' => 'application/json' }
208
+ post '/forest/actions/fail_action/hooks/change', params: JSON.dump(params), headers: headers
178
209
  expect(response.status).to eq(500)
179
210
  expect(JSON.parse(response.body)).to eq({'error' => 'Error in smart action load hook: hook must return an array of fields'})
180
211
  end
181
212
 
182
213
  it 'should reset value when enums has changed' do
183
214
  updated_enum = enum.clone.merge({:previousValue => nil, :value => 'a'}) # set value to a
184
- p = {recordIds: [1], fields: [updated_foo, updated_enum], collectionName: 'Island', changedField: 'foo'}
185
- post '/forest/actions/enums_action/hooks/change', params: JSON.dump(p), headers: { 'CONTENT_TYPE' => 'application/json' }
215
+ p = {
216
+ data: {
217
+ attributes: {
218
+ ids: [1],
219
+ fields: [updated_foo, updated_enum],
220
+ collection_name: 'Island',
221
+ changed_field: 'foo'
222
+ }
223
+ }
224
+ }
225
+ post '/forest/actions/enums_action/hooks/change', params: JSON.dump(p), headers: headers
186
226
  expect(response.status).to eq(200)
187
227
 
188
228
  expected_enum = updated_enum.clone.merge({ :enums => %w[c d e], :value => nil, :widgetEdit => nil})
@@ -195,8 +235,17 @@ describe 'Requesting Actions routes', :type => :request do
195
235
 
196
236
  it 'should not reset value when every enum values are in the enums definition' do
197
237
  updated_multiple_enum = multiple_enum.clone.merge({:previousValue => nil, :value => %w[c]})
198
- p = {recordIds: [1], fields: [foo, updated_multiple_enum], collectionName: 'Island', changedField: 'foo'}
199
- post '/forest/actions/multiple_enums_action/hooks/change', params: JSON.dump(p), headers: { 'CONTENT_TYPE' => 'application/json' }
238
+ p = {
239
+ data: {
240
+ attributes: {
241
+ ids: [1],
242
+ fields: [foo, updated_multiple_enum],
243
+ collection_name: 'Island',
244
+ changed_field: 'foo'
245
+ }
246
+ }
247
+ }
248
+ post '/forest/actions/multiple_enums_action/hooks/change', params: JSON.dump(p), headers: headers
200
249
  expect(response.status).to eq(200)
201
250
 
202
251
  expected_multiple_enum = updated_multiple_enum.clone.merge({ :enums => %w[c d z], :widgetEdit => nil, :value => %w[c]})
@@ -209,8 +258,18 @@ describe 'Requesting Actions routes', :type => :request do
209
258
 
210
259
  it 'should reset value when one of the enum values is not in the enums definition' do
211
260
  wrongly_updated_multiple_enum = multiple_enum.clone.merge({:previousValue => nil, :value => %w[a b]})
212
- p = {recordIds: [1], fields: [foo, wrongly_updated_multiple_enum], collectionName: 'Island', changedField: 'foo'}
213
- post '/forest/actions/multiple_enums_action/hooks/change', params: JSON.dump(p), headers: { 'CONTENT_TYPE' => 'application/json' }
261
+ p = {
262
+ data: {
263
+ attributes: {
264
+ ids: [1],
265
+ fields: [foo, wrongly_updated_multiple_enum],
266
+ collection_name: 'Island',
267
+ changed_field: 'foo'
268
+ }
269
+ }
270
+ }
271
+
272
+ post '/forest/actions/multiple_enums_action/hooks/change', params: JSON.dump(p), headers: headers
214
273
  expect(response.status).to eq(200)
215
274
 
216
275
  expected_multiple_enum = wrongly_updated_multiple_enum.clone.merge({ :enums => %w[c d z], :widgetEdit => nil, :value => nil })
@@ -222,4 +281,66 @@ describe 'Requesting Actions routes', :type => :request do
222
281
  end
223
282
  end
224
283
  end
284
+
285
+ describe 'calling the action' do
286
+ before(:each) do
287
+ allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { true }
288
+ end
289
+
290
+ let(:all_records) { false }
291
+ let(:params) {
292
+ {
293
+ data: {
294
+ attributes: {
295
+ collection_name: 'Island',
296
+ ids: ['1'],
297
+ all_records: all_records,
298
+ smart_action_id: 'Island-Test'
299
+ },
300
+ type: 'custom-action-requests'
301
+ },
302
+ timezone: 'Europe/Paris'
303
+ }
304
+ }
305
+
306
+ describe 'without scopes' do
307
+ it 'should respond 200 and perform the action' do
308
+ post '/forest/actions/test', params: JSON.dump(params), headers: headers
309
+ expect(response.status).to eq(200)
310
+ expect(JSON.parse(response.body)).to eq({'success' => 'You are OK.'})
311
+ end
312
+ end
313
+
314
+ describe 'with scopes' do
315
+ describe 'when record is in scope' do
316
+ let(:scope_filters) { JSON.generate({ field: 'name', operator: 'equal', value: 'Corsica' }) }
317
+
318
+ it 'should respond 200 and perform the action' do
319
+ post '/forest/actions/test', params: JSON.dump(params), headers: headers
320
+ expect(response.status).to eq(200)
321
+ expect(JSON.parse(response.body)).to eq({'success' => 'You are OK.'})
322
+ end
323
+ end
324
+
325
+ describe 'when record is out of scope' do
326
+ let(:scope_filters) { JSON.generate({ field: 'name', operator: 'equal', value: 'Ré' }) }
327
+
328
+ it 'should respond 400 and NOT perform the action' do
329
+ post '/forest/actions/test', params: JSON.dump(params), headers: headers
330
+ expect(response.status).to eq(400)
331
+ expect(JSON.parse(response.body)).to eq({ 'error' => 'Smart Action: target record not found' })
332
+ end
333
+
334
+ describe 'and all_records are targeted' do
335
+ let(:all_records) { true }
336
+
337
+ it 'should respond 200 and perform the action' do
338
+ post '/forest/actions/test', params: JSON.dump(params), headers: headers
339
+ expect(response.status).to eq(200)
340
+ expect(JSON.parse(response.body)).to eq({'success' => 'You are OK.'})
341
+ end
342
+ end
343
+ end
344
+ end
345
+ end
225
346
  end
@@ -45,7 +45,7 @@ describe "Authentications", type: :request do
45
45
 
46
46
  describe "GET /authentication/callback" do
47
47
  before() do
48
- response = '{"data":{"id":666,"attributes":{"first_name":"Alice","last_name":"Doe","email":"alice@forestadmin.com","teams":[1,2,3]}}}'
48
+ response = '{"data":{"id":666,"attributes":{"first_name":"Alice","last_name":"Doe","email":"alice@forestadmin.com","teams":[1,2,3],"role":"Test"}}}'
49
49
  allow(ForestLiana::ForestApiRequester).to receive(:get).with(
50
50
  "/liana/v2/renderings/42/authorization", { :headers => { "forest-token" => "THE-ACCESS-TOKEN" }, :query=> {} }
51
51
  ).and_return(
@@ -72,6 +72,7 @@ describe "Authentications", type: :request do
72
72
  "first_name" => 'Alice',
73
73
  "last_name" => 'Doe',
74
74
  "team" => 1,
75
+ "role" => "Test",
75
76
  }
76
77
 
77
78
  expect(decoded).to include(expected_token_data)
@@ -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({
@@ -4,9 +4,11 @@ module ForestLiana
4
4
  context 'on a disordered apimap' do
5
5
  apimap = {
6
6
  'meta': {
7
- 'orm_version': '4.34.9',
7
+ stack: {
8
+ 'orm_version': '4.34.9',
9
+ 'database_type': 'postgresql',
10
+ },
8
11
  'liana_version': '1.5.24',
9
- 'database_type': 'postgresql',
10
12
  liana: 'forest-rails',
11
13
  },
12
14
  'data': [{
@@ -165,8 +167,8 @@ module ForestLiana
165
167
  end
166
168
 
167
169
  it 'should sort the meta values' do
168
- expect(apimap_sorted['meta'].keys).to eq(
169
- ['database_type', 'liana', 'liana_version', 'orm_version'])
170
+ expect(apimap_sorted['meta'].keys).to eq(['liana', 'liana_version', 'stack'])
171
+ expect(apimap_sorted['meta']['stack'].keys).to eq(['database_type', 'orm_version'])
170
172
  end
171
173
  end
172
174
  end
@@ -281,7 +281,7 @@ module ForestLiana
281
281
  let(:result) { filter_parser.parse_condition(condition) }
282
282
 
283
283
  context 'on valid condition' do
284
- it { expect(result).to eq "\"trees\".\"name\" LIKE '%3'" }
284
+ it { expect(result).to eq "\"trees\".\"name\" LIKE ('%3')" }
285
285
  end
286
286
 
287
287
  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