forest_liana 7.0.0.beta.3 → 7.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 (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