forest_liana 9.22.17 → 9.22.18

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 10b9f231f7625702c1c63e6c2c2133360ccf2e9eaa6a68069a206669d902e93b
4
- data.tar.gz: 73c495fa6e0e02428eff8147b57387a640a244c91a4f745e7b0db7ac0d12d146
3
+ metadata.gz: d2876bbcc3a687f4bbb73e23af6d9ca98e91ebce886c749afdcca20eb2fc0bed
4
+ data.tar.gz: 2c2fcda6f31a2a176caab533e30e89fbea88f3dd8fbc9e870ffe9a29019e2284
5
5
  SHA512:
6
- metadata.gz: c3b01f5f3f3c4572fbad83d6b39436bb2eb12bd266fa61ac7048e0172d5922493b1cb1e0afeb0f6af0f07fe24ef45ac79804c298af2ee826a8221f542b850eb8
7
- data.tar.gz: 9f699625865575db5ebf7f7104b9004ecf8d091feb2401e490091b3eed83ef049eac4ebda850eeee565c04a80d52a5c7e4273b7c119643718be13d8c28c4e825
6
+ metadata.gz: 7ddfedbe5559ec9b55d869fec7e56e61f9e0560d574fd66d035180bb98e39bae8351739346f86d4faf720e07530c21d858660c0cfc8ae9a054d6ab3780d7088f
7
+ data.tar.gz: 12be328781a0a455c042c23668e3f3ca6946b437832f620398d97e7bc4bbd29d65733ff3f28b87dc7596e82e35539ed0eaff6f6a82efbb06015c7a7f82f39b1d
@@ -231,6 +231,8 @@ module ForestLiana
231
231
  end
232
232
 
233
233
  def apply_live_query_segment
234
+ ScopeManager.assert_can_execute_segment_query!(@user, @collection_name, @params[:segmentQuery])
235
+
234
236
  LiveQueryChecker.new(@params[:segmentQuery], 'Live Query Segment').validate
235
237
 
236
238
  begin
@@ -2,6 +2,7 @@ module ForestLiana
2
2
  class ScopeManager
3
3
  # 5 minutes expiration cache
4
4
  @@scope_cache_expiration_delta = 300
5
+ SEGMENT_UNION_SEPARATOR = '/*MULTI-SEGMENTS-QUERIES-UNION*/ UNION '
5
6
 
6
7
  def self.apply_scopes_on_records(records, user, collection_name, timezone)
7
8
  scope_filters = get_scope(collection_name, user)
@@ -65,25 +66,69 @@ module ForestLiana
65
66
  end
66
67
 
67
68
  def self.invalidate_scope_cache(rendering_id)
68
- Rails.cache.delete('forest.scopes.' + rendering_id.to_s)
69
- RequestCache.delete("forest.scopes.#{rendering_id}")
70
- end
71
-
72
- def self.fetch_scopes(rendering_id)
73
- RequestCache.fetch("forest.scopes.#{rendering_id}") do
74
- Rails.cache.fetch("forest.scopes.#{rendering_id}", expires_in: @@scope_cache_expiration_delta) do
75
- response = ForestLiana::ForestApiRequester.get("/liana/v4/permissions/renderings/#{rendering_id}")
76
- raise 'Unable to fetch scopes' unless response.is_a?(Net::HTTPOK)
77
- parsed_response = JSON.parse(response.body)
78
-
79
- {
80
- 'scopes' => decode_scope(parsed_response['collections']),
81
- 'team' => parsed_response['team']
82
- }
83
- end
69
+ Rails.cache.delete(scope_cache_key(rendering_id))
70
+ RequestCache.delete(scope_cache_key(rendering_id))
71
+ end
72
+
73
+ def self.assert_can_execute_segment_query!(user, collection_name, query)
74
+ return if Ability::ALLOWED_PERMISSION_LEVELS.include?(user['permission_level'])
75
+ raise Ability::Exceptions::AccessDenied.new unless query.is_a?(String)
76
+ return if segment_query_allowed?(segment_scopes(user['rendering_id']), collection_name, query)
77
+ return if segment_query_allowed?(segment_scopes(user['rendering_id'], force_fetch: true), collection_name, query)
78
+
79
+ raise Ability::Exceptions::AccessDenied.new
80
+ end
81
+
82
+ def self.segment_query_allowed?(scopes, collection_name, query)
83
+ allowed_queries = (scopes['segments'] || {})[collection_name]
84
+ return false if allowed_queries.nil?
85
+
86
+ allowed_set = allowed_queries.map { |allowed| normalize_segment_query(allowed) }
87
+ return true if allowed_set.include?(normalize_segment_query(query))
88
+
89
+ parts = query.split(SEGMENT_UNION_SEPARATOR)
90
+ parts.any? && parts.all? { |part| allowed_set.include?(normalize_segment_query(part)) }
91
+ end
92
+
93
+ def self.normalize_segment_query(query)
94
+ query.sub(/[\s;]+\z/, '').strip
95
+ end
96
+
97
+ def self.fetch_scopes(rendering_id, force_fetch: false)
98
+ key = scope_cache_key(rendering_id)
99
+ return RequestCache.write(key, cached_scopes(key, rendering_id, force_fetch)) if force_fetch
100
+
101
+ RequestCache.fetch(key) { cached_scopes(key, rendering_id, force_fetch) }
102
+ end
103
+
104
+ def self.segment_scopes(rendering_id, force_fetch: false)
105
+ fetch_scopes(rendering_id, force_fetch: force_fetch)
106
+ rescue StandardError
107
+ raise Errors::PermissionsUnavailableError.new
108
+ end
109
+
110
+ def self.cached_scopes(key, rendering_id, force_fetch)
111
+ Rails.cache.fetch(key, expires_in: @@scope_cache_expiration_delta, force: force_fetch) do
112
+ load_scopes(rendering_id)
84
113
  end
85
114
  end
86
115
 
116
+ def self.load_scopes(rendering_id)
117
+ response = ForestLiana::ForestApiRequester.get("/liana/v4/permissions/renderings/#{rendering_id}")
118
+ raise 'Unable to fetch scopes' unless response.is_a?(Net::HTTPOK)
119
+ parsed_response = JSON.parse(response.body)
120
+
121
+ {
122
+ 'scopes' => decode_scope(parsed_response['collections']),
123
+ 'segments' => decode_segments(parsed_response['collections']),
124
+ 'team' => parsed_response['team']
125
+ }
126
+ end
127
+
128
+ def self.scope_cache_key(rendering_id)
129
+ "forest.scopes.#{rendering_id}"
130
+ end
131
+
87
132
  def self.decode_scope(raw_scopes)
88
133
  scopes = {}
89
134
  raw_scopes.each do |collection_name, value|
@@ -92,5 +137,14 @@ module ForestLiana
92
137
 
93
138
  scopes
94
139
  end
140
+
141
+ def self.decode_segments(raw_collections)
142
+ raw_collections.each_with_object({}) do |(collection_name, value), segments|
143
+ segments[collection_name] = value['segments'] unless value['segments'].nil?
144
+ end
145
+ end
146
+
147
+ private_class_method :segment_query_allowed?, :normalize_segment_query, :segment_scopes, :cached_scopes,
148
+ :load_scopes, :scope_cache_key
95
149
  end
96
150
  end
@@ -1,3 +1,3 @@
1
1
  module ForestLiana
2
- VERSION = "9.22.17"
2
+ VERSION = "9.22.18"
3
3
  end
@@ -635,6 +635,89 @@ describe 'Requesting Tree resources', :type => :request do
635
635
  end
636
636
  end
637
637
 
638
+ describe 'live query segment permission' do
639
+ let(:listed_segment_query) { 'SELECT id FROM trees WHERE name IS NOT NULL' }
640
+ let(:unlisted_segment_query) { 'SELECT id FROM trees WHERE 1 = 0' }
641
+
642
+ restricted_token = JWT.encode({
643
+ id: 1,
644
+ email: 'michael.kelso@that70.show',
645
+ first_name: 'Michael',
646
+ last_name: 'Kelso',
647
+ team: 'Operations',
648
+ rendering_id: 16,
649
+ exp: Time.now.to_i + 2.weeks.to_i,
650
+ permission_level: 'user'
651
+ }, ForestLiana.auth_secret, 'HS256')
652
+
653
+ restricted_headers = headers.merge('Authorization' => "Bearer #{restricted_token}")
654
+
655
+ let(:list_params) {
656
+ {
657
+ fields: { 'Tree' => 'id,name' },
658
+ page: { 'number' => '1', 'size' => '10' },
659
+ searchExtended: '0',
660
+ sort: '-id',
661
+ timezone: 'Europe/Paris'
662
+ }
663
+ }
664
+
665
+ it 'refuses a listing whose segment query is not one of the team segments' do
666
+ get '/forest/Tree', params: list_params.merge(segmentQuery: unlisted_segment_query), headers: restricted_headers
667
+
668
+ expect(response.status).to eq(403)
669
+ expect(JSON.parse(response.body)['errors'][0]['name']).to eq('AccessDenied')
670
+ end
671
+
672
+ it 'serves the listing when the rendering lists that exact segment query' do
673
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(
674
+ 'scopes' => {},
675
+ 'segments' => { 'Tree' => [listed_segment_query] },
676
+ 'team' => { 'id' => '1', 'name' => 'Operations' }
677
+ )
678
+
679
+ get '/forest/Tree', params: list_params.merge(segmentQuery: listed_segment_query), headers: restricted_headers
680
+
681
+ expect(response.status).to eq(200)
682
+ expect(JSON.parse(response.body)['data'][0]['attributes']['name']).to eq('Lemon Tree')
683
+ end
684
+
685
+ it 'refuses the count the same way' do
686
+ get '/forest/Tree/count', params: { segmentQuery: unlisted_segment_query, timezone: 'Europe/Paris' }, headers: restricted_headers
687
+
688
+ expect(response.status).to eq(403)
689
+ end
690
+
691
+ it 'refuses the csv export the same way' do
692
+ get '/forest/Tree.csv', params: { segmentQuery: unlisted_segment_query, header: 'id,name' }, headers: restricted_headers
693
+
694
+ expect(response.status).to eq(403)
695
+ end
696
+
697
+ it 'refuses a select-all bulk delete without deleting anything' do
698
+ params = {
699
+ data: {
700
+ attributes: {
701
+ collection_name: 'Tree',
702
+ all_records: true,
703
+ all_records_subset_query: { segmentQuery: unlisted_segment_query }
704
+ }
705
+ }
706
+ }
707
+
708
+ delete '/forest/Tree', params: JSON.dump(params), headers: restricted_headers
709
+
710
+ expect(response.status).to eq(403)
711
+ expect(Tree.count).to eq(1)
712
+ end
713
+
714
+ it 'still serves an admin an unlisted segment query' do
715
+ get '/forest/Tree', params: list_params.merge(segmentQuery: unlisted_segment_query), headers: headers
716
+
717
+ expect(response.status).to eq(200)
718
+ end
719
+ end
720
+
638
721
  describe 'csv' do
639
722
  it 'should return CSV with correct headers and data' do
640
723
  params = {
@@ -305,6 +305,55 @@ module ForestLiana
305
305
  )
306
306
  end
307
307
 
308
+ describe 'with a live query segment on the "select all" selection' do
309
+ let(:segment_query) { "SELECT id FROM isle WHERE name = 'foo'" }
310
+
311
+ before do
312
+ Rails.cache.write('forest.has_permission', false)
313
+ end
314
+
315
+ def select_all_parameters_with_segment
316
+ select_all_params = params.deep_dup
317
+ select_all_params['data']['attributes'][:all_records] = true
318
+ select_all_params['data']['attributes'][:ids] = []
319
+ select_all_params['data']['attributes'][:all_records_subset_query] =
320
+ select_all_params['data']['attributes'][:all_records_subset_query].merge('segmentQuery' => segment_query)
321
+ ActionController::Parameters.new(select_all_params).permit!
322
+ end
323
+
324
+ it 'refuses with AccessDenied when the query is not one of the requester\'s segments' do
325
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(
326
+ 'scopes' => {}, 'segments' => {}, 'team' => {'id' => '1', 'name' => 'Operations'}
327
+ )
328
+ action['approvalRequired'] = [1]
329
+ action['userApprovalEnabled'] = [7]
330
+ smart_action_checker = ForestLiana::Ability::Permission::SmartActionChecker.new(
331
+ select_all_parameters_with_segment, Island, action, user, nil, forest_user
332
+ )
333
+
334
+ expect { smart_action_checker.can_execute? }
335
+ .to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
336
+ end
337
+
338
+ it 'resolves the record ids when the query is one of the requester\'s segments' do
339
+ allow(ForestLiana::ScopeManager).to receive(:fetch_scopes).and_return(
340
+ 'scopes' => {},
341
+ 'segments' => { 'Island' => [segment_query] },
342
+ 'team' => {'id' => '1', 'name' => 'Operations'}
343
+ )
344
+ action['approvalRequired'] = [1]
345
+ action['userApprovalEnabled'] = [7]
346
+ smart_action_checker = ForestLiana::Ability::Permission::SmartActionChecker.new(
347
+ select_all_parameters_with_segment, Island, action, user, nil, forest_user
348
+ )
349
+
350
+ expect { smart_action_checker.can_execute? }
351
+ .to raise_error(ForestLiana::Ability::Exceptions::RequireApproval) do |error|
352
+ expect(error.data[:recordIds]).to eq(['1'])
353
+ end
354
+ end
355
+ end
356
+
308
357
  it 'should raise RequireApproval error if match approvalRequiredConditions' do
309
358
  parameters = ActionController::Parameters.new(params).permit!
310
359
  action['approvalRequired'] = [1]
@@ -153,6 +153,13 @@ module ForestLiana
153
153
 
154
154
  describe 'when a live query segment uses a context variable' do
155
155
  let(:user) { { 'id' => '1', 'name' => 'Michel', 'rendering_id' => rendering_id } }
156
+ let(:scopes) {
157
+ {
158
+ 'scopes' => {},
159
+ 'segments' => { 'User' => ["SELECT id FROM users WHERE name = '{{currentUser.name}}'"] },
160
+ 'team' => {'id' => '1', 'name' => 'Operations'}
161
+ }
162
+ }
156
163
  let(:getter) do
157
164
  described_class.new(resource, {
158
165
  page: { size: pageSize, number: pageNumber },
@@ -209,6 +216,30 @@ module ForestLiana
209
216
  end
210
217
  end
211
218
 
219
+ describe 'when the live query segment is not one of the user\'s rendering segments' do
220
+ let(:getter) do
221
+ described_class.new(resource, {
222
+ page: { size: pageSize, number: pageNumber },
223
+ fields: { 'User' => 'id,name' },
224
+ segmentQuery: 'SELECT id FROM users WHERE name IS NOT NULL',
225
+ }, user)
226
+ end
227
+
228
+ it 'raises AccessDenied from the constructor, before any SQL runs' do
229
+ queries = []
230
+ subscriber = ActiveSupport::Notifications.subscribe('sql.active_record') do |*, payload|
231
+ queries << payload[:sql] unless payload[:name] == 'SCHEMA' || payload[:cached]
232
+ end
233
+ begin
234
+ expect { getter }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
235
+ ensure
236
+ ActiveSupport::Notifications.unsubscribe(subscriber)
237
+ end
238
+
239
+ expect(queries).to be_empty
240
+ end
241
+ end
242
+
212
243
  describe 'when there are more records than the page size' do
213
244
  describe 'when asking for the 1st page and 15 records' do
214
245
  let(:pageSize) { 15 }
@@ -70,6 +70,7 @@ module ForestLiana
70
70
  expect(Rails.cache.read('forest.scopes.' + rendering_id.to_s)).to eq(
71
71
  {
72
72
  'scopes' => {'User' => {'aggregator' => 'and', 'conditions'=>[{'field' => 'id', 'operator' => 'greater_than', 'value' => '1'}]}},
73
+ 'segments' => {'User' => []},
73
74
  'team' => {'id' => '1', 'name' => 'Operations'},
74
75
  })
75
76
  end
@@ -120,6 +121,7 @@ module ForestLiana
120
121
  expect(Rails.cache.read('forest.scopes.' + rendering_id.to_s)).to eq(
121
122
  {
122
123
  'scopes' => {'User' => {'aggregator' => 'and', 'conditions'=>[{'field' => 'email', 'operator' => 'equal', 'value' => '{{currentUser.email}}'}]}},
124
+ 'segments' => {'User' => []},
123
125
  'team' => {'id' => '1', 'name' => 'Operations'},
124
126
  }
125
127
  )
@@ -165,6 +167,7 @@ module ForestLiana
165
167
  expect(Rails.cache.read('forest.scopes.' + rendering_id.to_s)).to eq(
166
168
  {
167
169
  'scopes' => {},
170
+ 'segments' => {'User' => []},
168
171
  'team' => {'id' => '1', 'name' => 'Operations'},
169
172
  }
170
173
  )
@@ -172,6 +175,165 @@ module ForestLiana
172
175
  end
173
176
  end
174
177
 
178
+ describe '.assert_can_execute_segment_query!' do
179
+ let(:segment_query) { 'SELECT id FROM users WHERE name IS NOT NULL' }
180
+ let(:other_segment_query) { 'SELECT id FROM users WHERE id > 10;' }
181
+ let(:scopes_body) {
182
+ {
183
+ 'collections' => {
184
+ 'User' => { 'scope' => nil, 'segments' => [segment_query, other_segment_query] }
185
+ },
186
+ 'team' => {'id' => '1', 'name' => 'Operations'}
187
+ }
188
+ }
189
+
190
+ let(:rendering_path) { "/liana/v4/permissions/renderings/#{rendering_id}" }
191
+
192
+ def api_response(body)
193
+ body = JSON.generate(body)
194
+ response = Net::HTTPOK.new({}, 200, body)
195
+ allow(response).to receive(:body).and_return(body)
196
+ response
197
+ end
198
+
199
+ before do
200
+ allow(ForestLiana::ForestApiRequester).to receive(:get).and_return(api_response(scopes_body))
201
+ end
202
+
203
+ it 'accepts a query the rendering lists for the collection' do
204
+ expect {
205
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
206
+ }.not_to raise_error
207
+ end
208
+
209
+ it 'refuses a query the rendering does not list' do
210
+ expect {
211
+ described_class.assert_can_execute_segment_query!(user, collection_name, 'SELECT id FROM users WHERE 1 = 0')
212
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
213
+ end
214
+
215
+ it 'refuses a collection the rendering payload carries no segments for' do
216
+ expect {
217
+ described_class.assert_can_execute_segment_query!(user, 'Island', segment_query)
218
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
219
+ end
220
+
221
+ it 'accepts a UNION of configured segments, ignoring their trailing semicolons' do
222
+ union = "#{segment_query}/*MULTI-SEGMENTS-QUERIES-UNION*/ UNION SELECT id FROM users WHERE id > 10"
223
+
224
+ expect {
225
+ described_class.assert_can_execute_segment_query!(user, collection_name, union)
226
+ }.not_to raise_error
227
+ end
228
+
229
+ it 'refuses a UNION holding one unconfigured segment' do
230
+ union = "#{segment_query}/*MULTI-SEGMENTS-QUERIES-UNION*/ UNION SELECT id FROM users WHERE 1 = 0"
231
+
232
+ expect {
233
+ described_class.assert_can_execute_segment_query!(user, collection_name, union)
234
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
235
+ end
236
+
237
+ it 'accepts a configured query sent without its trailing semicolon' do
238
+ expect {
239
+ described_class.assert_can_execute_segment_query!(user, collection_name, 'SELECT id FROM users WHERE id > 10')
240
+ }.not_to raise_error
241
+ end
242
+
243
+ it 'accepts a configured segment that itself holds the union separator' do
244
+ union_segment = "#{segment_query}/*MULTI-SEGMENTS-QUERIES-UNION*/ UNION SELECT id FROM users WHERE id = 1"
245
+ scopes_body['collections']['User']['segments'] = [union_segment]
246
+ allow(ForestLiana::ForestApiRequester).to receive(:get).and_return(api_response(scopes_body))
247
+
248
+ expect {
249
+ described_class.assert_can_execute_segment_query!(user, collection_name, union_segment)
250
+ }.not_to raise_error
251
+ end
252
+
253
+ it 'answers 503 when the rendering cannot be fetched on a cold cache' do
254
+ allow(ForestLiana::ForestApiRequester).to receive(:get)
255
+ .and_return(Net::HTTPServiceUnavailable.new({}, 503, ''))
256
+
257
+ expect {
258
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
259
+ }.to raise_error(ForestLiana::Errors::PermissionsUnavailableError)
260
+ end
261
+
262
+ it 'refuses an empty query' do
263
+ expect {
264
+ described_class.assert_can_execute_segment_query!(user, collection_name, '')
265
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
266
+ end
267
+
268
+ it 'refuses a query that is not a string, without fetching the rendering' do
269
+ expect {
270
+ described_class.assert_can_execute_segment_query!(user, collection_name, [segment_query])
271
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
272
+ expect(ForestLiana::ForestApiRequester).not_to have_received(:get)
273
+ end
274
+
275
+ %w[admin editor developer].each do |permission_level|
276
+ it "lets a #{permission_level} run any query, without fetching the rendering" do
277
+ expect {
278
+ described_class.assert_can_execute_segment_query!(
279
+ user.merge('permission_level' => permission_level), collection_name, 'SELECT id FROM users WHERE 1 = 0'
280
+ )
281
+ }.not_to raise_error
282
+
283
+ expect(ForestLiana::ForestApiRequester).not_to have_received(:get)
284
+ end
285
+ end
286
+
287
+ describe 'when the cached rendering may be stale' do
288
+ let(:scopes_without_the_segment) {
289
+ body = scopes_body.deep_dup
290
+ body['collections']['User']['segments'] = []
291
+ body
292
+ }
293
+
294
+ it 'refetches the rendering once before refusing a fresher rendering could allow' do
295
+ allow(ForestLiana::ForestApiRequester).to receive(:get)
296
+ .and_return(api_response(scopes_without_the_segment), api_response(scopes_body))
297
+
298
+ expect {
299
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
300
+ }.not_to raise_error
301
+ expect(ForestLiana::ForestApiRequester).to have_received(:get).with(rendering_path).exactly(2).times
302
+ end
303
+
304
+ it 'still refuses when the refetched rendering denies it too' do
305
+ allow(ForestLiana::ForestApiRequester).to receive(:get)
306
+ .and_return(api_response(scopes_without_the_segment))
307
+
308
+ expect {
309
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
310
+ }.to raise_error(ForestLiana::Ability::Exceptions::AccessDenied)
311
+ expect(ForestLiana::ForestApiRequester).to have_received(:get).with(rendering_path).exactly(2).times
312
+ end
313
+
314
+ it 'keeps the cached rendering and answers 503 when the refetch fails' do
315
+ unavailable = Net::HTTPServiceUnavailable.new({}, 503, '')
316
+ allow(ForestLiana::ForestApiRequester).to receive(:get)
317
+ .and_return(api_response(scopes_without_the_segment), unavailable)
318
+
319
+ expect {
320
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
321
+ }.to raise_error(ForestLiana::Errors::PermissionsUnavailableError)
322
+ expect(Rails.cache.read("forest.scopes.#{rendering_id}")['segments']).to eq('User' => [])
323
+ end
324
+
325
+ it 'refetches a cache entry from before segments were cached, before deciding' do
326
+ Rails.cache.write("forest.scopes.#{rendering_id}", {'scopes' => {}, 'team' => {}})
327
+ allow(ForestLiana::ForestApiRequester).to receive(:get).and_return(api_response(scopes_body))
328
+
329
+ expect {
330
+ described_class.assert_can_execute_segment_query!(user, collection_name, segment_query)
331
+ }.not_to raise_error
332
+ expect(ForestLiana::ForestApiRequester).to have_received(:get).with(rendering_path).once
333
+ end
334
+ end
335
+ end
336
+
175
337
  describe '#append_scope_for_user' do
176
338
  let(:filters) { described_class.append_scope_for_user(existing_filter, user, collection_name) }
177
339
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_liana
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.22.17
4
+ version: 9.22.18
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sandro Munda