forest_liana 7.0.0.beta.3 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) 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 +15 -6
  6. data/app/controllers/forest_liana/scopes_controller.rb +20 -0
  7. data/app/controllers/forest_liana/smart_actions_controller.rb +39 -3
  8. data/app/controllers/forest_liana/stats_controller.rb +5 -5
  9. data/app/services/forest_liana/filters_parser.rb +8 -4
  10. data/app/services/forest_liana/has_many_dissociator.rb +2 -2
  11. data/app/services/forest_liana/has_many_getter.rb +2 -2
  12. data/app/services/forest_liana/leaderboard_stat_getter.rb +20 -14
  13. data/app/services/forest_liana/line_stat_getter.rb +4 -2
  14. data/app/services/forest_liana/permissions_checker.rb +3 -4
  15. data/app/services/forest_liana/permissions_getter.rb +2 -2
  16. data/app/services/forest_liana/pie_stat_getter.rb +6 -3
  17. data/app/services/forest_liana/resource_getter.rb +6 -3
  18. data/app/services/forest_liana/resource_updater.rb +5 -2
  19. data/app/services/forest_liana/resources_getter.rb +6 -5
  20. data/app/services/forest_liana/scope_manager.rb +102 -0
  21. data/app/services/forest_liana/search_query_builder.rb +6 -3
  22. data/app/services/forest_liana/stat_getter.rb +2 -1
  23. data/app/services/forest_liana/value_stat_getter.rb +4 -2
  24. data/config/routes.rb +3 -1
  25. data/lib/forest_liana/version.rb +1 -1
  26. data/spec/dummy/app/controllers/forest/islands_controller.rb +5 -0
  27. data/spec/dummy/config/routes.rb +4 -0
  28. data/spec/dummy/lib/forest_liana/collections/island.rb +7 -0
  29. data/spec/requests/actions_controller_spec.rb +144 -23
  30. data/spec/requests/resources_spec.rb +2 -0
  31. data/spec/services/forest_liana/filters_parser_spec.rb +1 -1
  32. data/spec/services/forest_liana/has_many_getter_spec.rb +116 -0
  33. data/spec/services/forest_liana/line_stat_getter_spec.rb +14 -6
  34. data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +1 -3
  35. data/spec/services/forest_liana/pie_stat_getter_spec.rb +114 -0
  36. data/spec/services/forest_liana/resource_updater_spec.rb +116 -0
  37. data/spec/services/forest_liana/resources_getter_spec.rb +68 -1
  38. data/spec/services/forest_liana/scope_manager_spec.rb +232 -0
  39. data/spec/services/forest_liana/value_stat_getter_spec.rb +96 -0
  40. metadata +20 -15
  41. data/app/services/forest_liana/scope_validator.rb +0 -98
  42. data/test/services/forest_liana/has_many_getter_test.rb +0 -75
  43. data/test/services/forest_liana/pie_stat_getter_test.rb +0 -29
  44. data/test/services/forest_liana/resource_updater_test.rb +0 -86
  45. data/test/services/forest_liana/scope_validator_test.rb +0 -185
  46. data/test/services/forest_liana/value_stat_getter_test.rb +0 -71
@@ -7,7 +7,6 @@ module ForestLiana
7
7
  @@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
8
8
 
9
9
  def initialize(resource, permission_name, rendering_id, user_id: nil, smart_action_request_info: nil, collection_list_parameters: Hash.new, query_request_info: nil)
10
-
11
10
  @collection_name = resource.present? ? ForestLiana.name_for(resource) : nil
12
11
  @permission_name = permission_name
13
12
  @rendering_id = rendering_id
@@ -119,7 +118,7 @@ module ForestLiana
119
118
  permissions = @@renderings_cached[@rendering_id]
120
119
  permissions && permissions['stats'] && permissions['stats']['queries']
121
120
  end
122
-
121
+
123
122
  def get_stat_with_parameters_content(statPermissionType)
124
123
  permissions = @@renderings_cached[@rendering_id]
125
124
  permissions && permissions['stats'] && permissions['stats'][statPermissionType]
@@ -163,7 +162,7 @@ module ForestLiana
163
162
 
164
163
  return false unless segments_queries_permissions
165
164
 
166
- # NOTICE: @query_request_info matching an existing segment query
165
+ # NOTICE: @query_request_info matching an existing segment query
167
166
  return segments_queries_permissions.include? @collection_list_parameters[:segmentQuery]
168
167
  end
169
168
 
@@ -172,7 +171,7 @@ module ForestLiana
172
171
 
173
172
  return false unless live_queries_permissions
174
173
 
175
- # NOTICE: @query_request_info matching an existing live query
174
+ # NOTICE: @query_request_info matching an existing live query
176
175
  return live_queries_permissions.include? @query_request_info
177
176
  end
178
177
 
@@ -23,7 +23,7 @@ module ForestLiana
23
23
  # },
24
24
  # },
25
25
  # },
26
- # rederings => {
26
+ # renderings => {
27
27
  # {rendering_id} => {
28
28
  # {collection_id} => {
29
29
  # segments => ['query1', 'query2']
@@ -32,7 +32,7 @@ module ForestLiana
32
32
  # }
33
33
  # }
34
34
  # With `rendering_specific_only` this returns only the permissions related data specific to the provided rendering
35
- # For now this only includes scopes
35
+ # For now this only includes scopes (but scopes are not used anymore in permissions)
36
36
  def get_permissions_for_rendering(rendering_id, rendering_specific_only: false)
37
37
  begin
38
38
  query_parameters = { 'renderingId' => rendering_id }
@@ -7,8 +7,10 @@ module ForestLiana
7
7
  timezone_offset = @params[:timezone].to_i
8
8
  resource = get_resource().eager_load(@includes)
9
9
 
10
- unless @params[:filters].blank?
11
- resource = FiltersParser.new(@params[:filters], resource, @params[:timezone]).apply_filters
10
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @resource.name)
11
+
12
+ unless filters.blank?
13
+ resource = FiltersParser.new(filters, resource, @params[:timezone]).apply_filters
12
14
  end
13
15
 
14
16
  result = resource
@@ -53,7 +55,8 @@ module ForestLiana
53
55
  if @params[:aggregate].downcase == 'sum'
54
56
  field = @params[:aggregate_field].downcase
55
57
  else
56
- field = Rails::VERSION::MAJOR >= 5 || @includes.size > 0 ? 'id' : 'all'
58
+ # `count_id` is required only for rails v5
59
+ field = Rails::VERSION::MAJOR == 5 || @includes.size > 0 ? 'id' : 'all'
57
60
  end
58
61
  "#{@params[:aggregate].downcase}_#{field} #{order}"
59
62
  end
@@ -2,16 +2,19 @@ module ForestLiana
2
2
  class ResourceGetter < 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
- @collection_name = ForestLiana.name_for(@resource)
8
+ @collection_name = ForestLiana.name_for(resource)
9
+ @user = forest_user
9
10
  @collection = get_collection(@collection_name)
10
11
  compute_includes()
11
12
  end
12
13
 
13
14
  def perform
14
- @record = get_resource().eager_load(@includes).find(@params[:id])
15
+ records = get_resource().eager_load(@includes)
16
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(records, @user, @collection_name, @params[:timezone])
17
+ @record = scoped_records.find(@params[:id])
15
18
  end
16
19
  end
17
20
  end
@@ -3,15 +3,18 @@ module ForestLiana
3
3
  attr_accessor :record
4
4
  attr_accessor :errors
5
5
 
6
- def initialize(resource, params)
6
+ def initialize(resource, params, forest_user)
7
7
  @resource = resource
8
8
  @params = params
9
9
  @errors = nil
10
+ @user = forest_user
10
11
  end
11
12
 
12
13
  def perform
13
14
  begin
14
- @record = @resource.find(@params[:id])
15
+ collection_name = ForestLiana.name_for(@resource)
16
+ scoped_records = ForestLiana::ScopeManager.apply_scopes_on_records(@resource, @user, collection_name, @params[:timezone])
17
+ @record = scoped_records.find(@params[:id])
15
18
 
16
19
  if has_strong_parameter
17
20
  @record.update(resource_params)
@@ -4,7 +4,7 @@ module ForestLiana
4
4
  attr_reader :includes
5
5
  attr_reader :records_count
6
6
 
7
- def initialize(resource, params)
7
+ def initialize(resource, params, forest_user)
8
8
  @resource = resource
9
9
  @params = params
10
10
  @count_needs_includes = false
@@ -14,12 +14,13 @@ module ForestLiana
14
14
  @field_names_requested = field_names_requested
15
15
  get_segment
16
16
  compute_includes
17
- @search_query_builder = SearchQueryBuilder.new(@params, @includes, @collection)
17
+ @user = forest_user
18
+ @search_query_builder = SearchQueryBuilder.new(@params, @includes, @collection, forest_user)
18
19
 
19
20
  prepare_query
20
21
  end
21
22
 
22
- def self.get_ids_from_request(params)
23
+ def self.get_ids_from_request(params, user)
23
24
  attributes = params.dig('data', 'attributes')
24
25
  has_body_attributes = attributes != nil
25
26
  is_select_all_records_query = has_body_attributes && attributes[:all_records] == true
@@ -45,11 +46,11 @@ module ForestLiana
45
46
  collection: parent_collection_name,
46
47
  id: attributes[:parent_collection_id],
47
48
  association_name: attributes[:parent_association_name],
48
- }))
49
+ }), user)
49
50
  else
50
51
  collection_name = attributes[:collection_name]
51
52
  model = ForestLiana::SchemaUtils.find_model_from_collection_name(collection_name)
52
- resources_getter = ForestLiana::ResourcesGetter.new(model, attributes)
53
+ resources_getter = ForestLiana::ResourcesGetter.new(model, attributes, user)
53
54
  end
54
55
 
55
56
  # NOTICE: build IDs list.
@@ -0,0 +1,102 @@
1
+ module ForestLiana
2
+ class ScopeManager
3
+ @@scopes_cache = Hash.new
4
+ # 5 minutes exipration cache
5
+ @@scope_cache_expiration_delta = 300
6
+
7
+ def self.apply_scopes_on_records(records, forest_user, collection_name, timezone)
8
+ scope_filters = get_scope_for_user(forest_user, collection_name, as_string: true)
9
+
10
+ return records if scope_filters.blank?
11
+
12
+ FiltersParser.new(scope_filters, records, timezone).apply_filters
13
+ end
14
+
15
+ def self.append_scope_for_user(existing_filter, user, collection_name)
16
+ scope_filter = get_scope_for_user(user, collection_name, as_string: true)
17
+ filters = [existing_filter, scope_filter].compact
18
+
19
+ case filters.length
20
+ when 0
21
+ nil
22
+ when 1
23
+ filters[0]
24
+ else
25
+ "{\"aggregator\":\"and\",\"conditions\":[#{existing_filter},#{scope_filter}]}"
26
+ end
27
+ end
28
+
29
+ def self.get_scope_for_user(user, collection_name, as_string: false)
30
+ raise 'Missing required rendering_id' unless user['rendering_id']
31
+ raise 'Missing required collection_name' unless collection_name
32
+
33
+ collection_scope = get_collection_scope(user['rendering_id'], collection_name)
34
+
35
+ return nil unless collection_scope
36
+
37
+ filters = format_dynamic_values(user['id'], collection_scope)
38
+
39
+ as_string && filters ? JSON.generate(filters) : filters
40
+ end
41
+
42
+ def self.get_collection_scope(rendering_id, collection_name)
43
+ if !@@scopes_cache[rendering_id]
44
+ # when scope cache is unset wait for the refresh
45
+ refresh_scopes_cache(rendering_id)
46
+ elsif has_cache_expired?(rendering_id)
47
+ # when cache expired refresh the scopes without waiting for it
48
+ Thread.new { refresh_scopes_cache(rendering_id) }
49
+ end
50
+
51
+ @@scopes_cache[rendering_id][:scopes][collection_name]
52
+ end
53
+
54
+ def self.has_cache_expired?(rendering_id)
55
+ rendering_scopes = @@scopes_cache[rendering_id]
56
+ return true unless rendering_scopes
57
+
58
+ second_since_last_fetch = Time.now - rendering_scopes[:fetched_at]
59
+ second_since_last_fetch >= @@scope_cache_expiration_delta
60
+ end
61
+
62
+ def self.refresh_scopes_cache(rendering_id)
63
+ scopes = fetch_scopes(rendering_id)
64
+ @@scopes_cache[rendering_id] = {
65
+ :fetched_at => Time.now,
66
+ :scopes => scopes
67
+ }
68
+ end
69
+
70
+ def self.fetch_scopes(rendering_id)
71
+ query_parameters = { 'renderingId' => rendering_id }
72
+ response = ForestLiana::ForestApiRequester.get('/liana/scopes', query: query_parameters)
73
+
74
+ if response.is_a?(Net::HTTPOK)
75
+ JSON.parse(response.body)
76
+ else
77
+ raise 'Unable to fetch scopes'
78
+ end
79
+ end
80
+
81
+ def self.format_dynamic_values(user_id, collection_scope)
82
+ filter = collection_scope.dig('scope', 'filter')
83
+ return nil unless filter
84
+
85
+ dynamic_scopes_values = collection_scope.dig('scope', 'dynamicScopesValues')
86
+
87
+ # Only goes one level deep as required for now
88
+ filter['conditions'].map do |condition|
89
+ value = condition['value']
90
+ if value.is_a?(String) && value.start_with?('$currentUser')
91
+ condition['value'] = dynamic_scopes_values.dig('users', user_id, value)
92
+ end
93
+ end
94
+
95
+ filter
96
+ end
97
+
98
+ def self.invalidate_scope_cache(rendering_id)
99
+ @@scopes_cache.delete(rendering_id)
100
+ end
101
+ end
102
+ end
@@ -4,12 +4,13 @@ module ForestLiana
4
4
 
5
5
  attr_reader :fields_searched
6
6
 
7
- def initialize(params, includes, collection)
7
+ def initialize(params, includes, collection, user)
8
8
  @params = params
9
9
  @includes = includes
10
10
  @collection = collection
11
11
  @fields_searched = []
12
12
  @search = @params[:search]
13
+ @user = user
13
14
  end
14
15
 
15
16
  def perform(resource)
@@ -18,8 +19,10 @@ module ForestLiana
18
19
  ForestLiana::QueryHelper.get_tables_associated_to_relations_name(@resource)
19
20
  @records = search_param
20
21
 
21
- unless @params[:filters].blank?
22
- @records = FiltersParser.new(@params[:filters], @records, @params[:timezone]).apply_filters
22
+ filters = ForestLiana::ScopeManager.append_scope_for_user(@params[:filters], @user, @collection.name)
23
+
24
+ unless filters.blank?
25
+ @records = FiltersParser.new(filters, @records, @params[:timezone]).apply_filters
23
26
  end
24
27
 
25
28
  if @search
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "7.0.0.beta.3"
2
+ VERSION = "7.0.0"
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
@@ -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