forest_liana 6.0.5 → 6.1.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.
- checksums.yaml +4 -4
- data/app/controllers/forest_liana/stats_controller.rb +47 -2
- data/app/services/forest_liana/permissions_checker.rb +52 -3
- data/lib/forest_liana/version.rb +1 -1
- data/spec/requests/stats_spec.rb +114 -0
- data/spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb +3 -3
- data/spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb +2 -2
- data/spec/services/forest_liana/permissions_checker_live_queries_spec.rb +131 -0
- metadata +6 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de6de5cae6e0ce03e49a351ee0ec5ead80b6ac45fc7f56a6b6912b663c7b85c3
|
4
|
+
data.tar.gz: 03d99b9f6ef599ec4c18446c50f9f4c7b24c09771e626954cdfb800ec25a55ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea5e991a8476f95d7571ff1bf5e9dfef006c119c67857d18967f80810417db15122f2b64e922906e95d1401b5e71df099705718f431ec481de5a9da6c0d7310f
|
7
|
+
data.tar.gz: 7fde6b7720bdf881a3fc5bfcab28d4ca05092fe7a45a5dc8d3c40d4576ff4b0bb6c423eb500b4bc485181840ef8685b5167ab92501aa1533adbbda7e3f28ac5f
|
@@ -1,9 +1,21 @@
|
|
1
1
|
module ForestLiana
|
2
2
|
class StatsController < ForestLiana::ApplicationController
|
3
3
|
if Rails::VERSION::MAJOR < 4
|
4
|
-
before_filter :
|
4
|
+
before_filter only: [:get] do
|
5
|
+
find_resource()
|
6
|
+
check_permission('statWithParameters')
|
7
|
+
end
|
8
|
+
before_filter only: [:get_with_live_query] do
|
9
|
+
check_permission('liveQueries')
|
10
|
+
end
|
5
11
|
else
|
6
|
-
before_action :
|
12
|
+
before_action only: [:get] do
|
13
|
+
find_resource()
|
14
|
+
check_permission('statWithParameters')
|
15
|
+
end
|
16
|
+
before_action only: [:get_with_live_query] do
|
17
|
+
check_permission('liveQueries')
|
18
|
+
end
|
7
19
|
end
|
8
20
|
|
9
21
|
CHART_TYPE_VALUE = 'Value'
|
@@ -64,5 +76,38 @@ module ForestLiana
|
|
64
76
|
render json: {status: 404}, status: :not_found, serializer: nil
|
65
77
|
end
|
66
78
|
end
|
79
|
+
|
80
|
+
def get_live_query_request_info
|
81
|
+
params['query']
|
82
|
+
end
|
83
|
+
|
84
|
+
def get_stat_parameter_request_info
|
85
|
+
parameters = Rails::VERSION::MAJOR < 5 ? params.dup : params.permit(params.keys).to_h;
|
86
|
+
|
87
|
+
# Notice: Removes useless properties
|
88
|
+
parameters.delete('timezone');
|
89
|
+
parameters.delete('controller');
|
90
|
+
parameters.delete('action');
|
91
|
+
|
92
|
+
return parameters;
|
93
|
+
end
|
94
|
+
|
95
|
+
def check_permission(permission_name)
|
96
|
+
begin
|
97
|
+
query_request = permission_name == 'liveQueries' ? get_live_query_request_info : get_stat_parameter_request_info;
|
98
|
+
checker = ForestLiana::PermissionsChecker.new(
|
99
|
+
nil,
|
100
|
+
permission_name,
|
101
|
+
@rendering_id,
|
102
|
+
user_id: forest_user['id'],
|
103
|
+
query_request_info: query_request
|
104
|
+
)
|
105
|
+
|
106
|
+
return head :forbidden unless checker.is_authorized?
|
107
|
+
rescue => error
|
108
|
+
FOREST_LOGGER.error "Stats execution error: #{error}"
|
109
|
+
render serializer: nil, json: { status: 400 }, status: :bad_request
|
110
|
+
end
|
111
|
+
end
|
67
112
|
end
|
68
113
|
end
|
@@ -6,13 +6,16 @@ module ForestLiana
|
|
6
6
|
# TODO: handle cache scopes per rendering
|
7
7
|
@@expiration_in_seconds = (ENV['FOREST_PERMISSIONS_EXPIRATION_IN_SECONDS'] || 3600).to_i
|
8
8
|
|
9
|
-
def initialize(resource, permission_name, rendering_id, user_id
|
10
|
-
|
11
|
-
@collection_name = ForestLiana.name_for(resource)
|
9
|
+
def initialize(resource, permission_name, rendering_id, user_id: nil, smart_action_request_info: nil, collection_list_parameters: nil, query_request_info: nil)
|
10
|
+
|
11
|
+
@collection_name = resource.present? ? ForestLiana.name_for(resource) : nil
|
12
12
|
@permission_name = permission_name
|
13
13
|
@rendering_id = rendering_id
|
14
|
+
|
15
|
+
@user_id = user_id
|
14
16
|
@smart_action_request_info = smart_action_request_info
|
15
17
|
@collection_list_parameters = collection_list_parameters
|
18
|
+
@query_request_info = query_request_info
|
16
19
|
end
|
17
20
|
|
18
21
|
def is_authorized?
|
@@ -48,6 +51,16 @@ module ForestLiana
|
|
48
51
|
|
49
52
|
def is_allowed
|
50
53
|
permissions = get_permissions_content
|
54
|
+
|
55
|
+
# NOTICE: check liveQueries permissions
|
56
|
+
if @permission_name === 'liveQueries'
|
57
|
+
return live_query_allowed?
|
58
|
+
elsif @permission_name === 'statWithParameters'
|
59
|
+
return stat_with_parameters_allowed?
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
|
51
64
|
if permissions && permissions[@collection_name] &&
|
52
65
|
permissions[@collection_name]['collection']
|
53
66
|
if @permission_name === 'actions'
|
@@ -98,6 +111,16 @@ module ForestLiana
|
|
98
111
|
permissions && permissions['data'] && permissions['data']['collections']
|
99
112
|
end
|
100
113
|
|
114
|
+
def get_live_query_permissions_content
|
115
|
+
permissions = get_permissions
|
116
|
+
permissions && permissions['stats'] && permissions['stats']['queries']
|
117
|
+
end
|
118
|
+
|
119
|
+
def get_stat_with_parameters_content(statPermissionType)
|
120
|
+
permissions = get_permissions
|
121
|
+
permissions && permissions['stats'] && permissions['stats'][statPermissionType]
|
122
|
+
end
|
123
|
+
|
101
124
|
def get_last_fetch
|
102
125
|
permissions = get_permissions
|
103
126
|
permissions && permissions['last_fetch']
|
@@ -138,6 +161,32 @@ module ForestLiana
|
|
138
161
|
).is_scope_in_request?(@collection_list_parameters)
|
139
162
|
end
|
140
163
|
|
164
|
+
def live_query_allowed?
|
165
|
+
live_queries_permissions = get_live_query_permissions_content
|
166
|
+
|
167
|
+
return false unless live_queries_permissions
|
168
|
+
|
169
|
+
# NOTICE: @query_request_info matching an existing live query
|
170
|
+
return live_queries_permissions.include? @query_request_info
|
171
|
+
end
|
172
|
+
|
173
|
+
def stat_with_parameters_allowed?
|
174
|
+
permissionType = @query_request_info['type'].downcase + 's'
|
175
|
+
pool_permissions = get_stat_with_parameters_content(permissionType)
|
176
|
+
|
177
|
+
return false unless pool_permissions
|
178
|
+
|
179
|
+
# NOTICE: equivalent to Object.values in js
|
180
|
+
array_query_request_info = @query_request_info.values
|
181
|
+
|
182
|
+
# NOTICE: pool_permissions contains the @query_request_info
|
183
|
+
# we use the intersection between statPermission and @query_request_info
|
184
|
+
return pool_permissions.any? {
|
185
|
+
|statPermission|
|
186
|
+
(array_query_request_info & statPermission.values) == array_query_request_info;
|
187
|
+
}
|
188
|
+
end
|
189
|
+
|
141
190
|
def date_difference_in_seconds(date1, date2)
|
142
191
|
(date1 - date2).to_i
|
143
192
|
end
|
data/lib/forest_liana/version.rb
CHANGED
@@ -0,0 +1,114 @@
|
|
1
|
+
require 'rails_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe "Stats", type: :request do
|
5
|
+
|
6
|
+
token = JWT.encode({
|
7
|
+
id: 38,
|
8
|
+
email: 'michael.kelso@that70.show',
|
9
|
+
first_name: 'Michael',
|
10
|
+
last_name: 'Kelso',
|
11
|
+
team: 'Operations',
|
12
|
+
rendering_id: 16,
|
13
|
+
exp: Time.now.to_i + 2.weeks.to_i
|
14
|
+
}, ForestLiana.auth_secret, 'HS256')
|
15
|
+
|
16
|
+
headers = {
|
17
|
+
'Accept' => 'application/json',
|
18
|
+
'Content-Type' => 'application/json',
|
19
|
+
'Authorization' => "Bearer #{token}"
|
20
|
+
}
|
21
|
+
|
22
|
+
let(:schema) {
|
23
|
+
[
|
24
|
+
ForestLiana::Model::Collection.new({
|
25
|
+
name: 'Products',
|
26
|
+
fields: [],
|
27
|
+
actions: []
|
28
|
+
})
|
29
|
+
]
|
30
|
+
}
|
31
|
+
|
32
|
+
before do
|
33
|
+
allow(ForestLiana).to receive(:apimap).and_return(schema)
|
34
|
+
end
|
35
|
+
|
36
|
+
before(:each) do
|
37
|
+
allow(ForestLiana::IpWhitelist).to receive(:retrieve) { true }
|
38
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_whitelist_retrieved) { true }
|
39
|
+
allow(ForestLiana::IpWhitelist).to receive(:is_ip_valid) { true }
|
40
|
+
|
41
|
+
allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { true }
|
42
|
+
|
43
|
+
allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:perform) { true }
|
44
|
+
allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:perform) { true }
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
describe 'POST /stats/:collection' do
|
50
|
+
params = { type: 'Value', collection: 'User', aggregate: 'Count' }
|
51
|
+
|
52
|
+
it 'should respond 200' do
|
53
|
+
data = ForestLiana::Model::Stat.new(value: { countCurrent: 0, countPrevious: 0 })
|
54
|
+
allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:record) { data }
|
55
|
+
# NOTICE: bypass : find_resource error
|
56
|
+
allow_any_instance_of(ForestLiana::StatsController).to receive(:find_resource) { true }
|
57
|
+
allow(ForestLiana::QueryHelper).to receive(:get_one_association_names_symbol) { true }
|
58
|
+
|
59
|
+
post '/forest/stats/Products', params: JSON.dump(params), headers: headers
|
60
|
+
expect(response.status).to eq(200)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'should respond 401 with no headers' do
|
64
|
+
post '/forest/stats/Products', params: JSON.dump(params)
|
65
|
+
expect(response.status).to eq(401)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'should respond 404 with non existing collection' do
|
69
|
+
allow_any_instance_of(ForestLiana::ValueStatGetter).to receive(:record) { nil }
|
70
|
+
|
71
|
+
post '/forest/stats/NoCollection', params: {}, headers: headers
|
72
|
+
expect(response.status).to eq(404)
|
73
|
+
end
|
74
|
+
|
75
|
+
# it 'should respond 403 Forbidden' do
|
76
|
+
# allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { false }
|
77
|
+
|
78
|
+
# post '/forest/stats/Products', params: JSON.dump(params), headers: headers
|
79
|
+
# expect(response.status).to eq(403)
|
80
|
+
# end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'POST /stats' do
|
84
|
+
params = { query: 'SELECT COUNT(*) AS value FROM products;' }
|
85
|
+
|
86
|
+
it 'should respond 200' do
|
87
|
+
data = ForestLiana::Model::Stat.new(value: { value: 0, objective: 0 })
|
88
|
+
allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:record) { data }
|
89
|
+
|
90
|
+
post '/forest/stats', params: JSON.dump(params), headers: headers
|
91
|
+
expect(response.status).to eq(200)
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'should respond 401 with no headers' do
|
95
|
+
post '/forest/stats', params: JSON.dump(params)
|
96
|
+
expect(response.status).to eq(401)
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'should respond 403 Forbidden' do
|
100
|
+
allow_any_instance_of(ForestLiana::PermissionsChecker).to receive(:is_authorized?) { false }
|
101
|
+
|
102
|
+
post '/forest/stats', params: JSON.dump(params), headers: headers
|
103
|
+
expect(response.status).to eq(403)
|
104
|
+
end
|
105
|
+
|
106
|
+
it 'should respond 422 with unprocessable query' do
|
107
|
+
allow_any_instance_of(ForestLiana::QueryStatGetter).to receive(:perform) { raise ForestLiana::Errors::LiveQueryError.new }
|
108
|
+
|
109
|
+
post '/forest/stats', params: JSON.dump(params), headers: headers
|
110
|
+
expect(response.status).to eq(422)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
@@ -109,7 +109,7 @@ module ForestLiana
|
|
109
109
|
|
110
110
|
describe 'handling cache' do
|
111
111
|
let(:collection_name) { 'all_rights_collection' }
|
112
|
-
let(:fake_ressource) {
|
112
|
+
let(:fake_ressource) { collection_name }
|
113
113
|
let(:default_rendering_id) { 1 }
|
114
114
|
|
115
115
|
context 'when calling twice the same permissions' do
|
@@ -191,7 +191,7 @@ module ForestLiana
|
|
191
191
|
|
192
192
|
|
193
193
|
context 'scopes cache' do
|
194
|
-
let(:fake_ressource) {
|
194
|
+
let(:fake_ressource) { collection_name }
|
195
195
|
let(:rendering_id) { 1 }
|
196
196
|
let(:collection_name) { 'custom' }
|
197
197
|
let(:scope_permissions) { { rendering_id => { 'custom' => nil } } }
|
@@ -349,7 +349,7 @@ module ForestLiana
|
|
349
349
|
describe '#is_authorized?' do
|
350
350
|
# Resource is only used to retrieve the collection name as it's stubbed it does not
|
351
351
|
# need to be defined
|
352
|
-
let(:fake_ressource) {
|
352
|
+
let(:fake_ressource) { collection_name }
|
353
353
|
let(:default_rendering_id) { nil }
|
354
354
|
let(:api_permissions) { default_api_permissions }
|
355
355
|
let(:collection_name) { 'all_rights_collection' }
|
@@ -132,7 +132,7 @@ module ForestLiana
|
|
132
132
|
|
133
133
|
describe 'handling cache' do
|
134
134
|
let(:collection_name) { 'all_rights_collection_boolean' }
|
135
|
-
let(:fake_ressource) {
|
135
|
+
let(:fake_ressource) { collection_name }
|
136
136
|
let(:default_rendering_id) { 1 }
|
137
137
|
|
138
138
|
context 'collections cache' do
|
@@ -396,7 +396,7 @@ module ForestLiana
|
|
396
396
|
describe '#is_authorized?' do
|
397
397
|
# Resource is only used to retrieve the collection name as it's stub it does not
|
398
398
|
# need to be defined
|
399
|
-
let(:fake_ressource) {
|
399
|
+
let(:fake_ressource) { collection_name }
|
400
400
|
let(:default_rendering_id) { nil }
|
401
401
|
let(:api_permissions) { default_api_permissions }
|
402
402
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
describe PermissionsChecker do
|
3
|
+
before(:each) do
|
4
|
+
described_class.empty_cache
|
5
|
+
end
|
6
|
+
|
7
|
+
let(:user_id) { 1 }
|
8
|
+
let(:schema) {
|
9
|
+
[
|
10
|
+
ForestLiana::Model::Collection.new({
|
11
|
+
name: 'all_rights_collection_boolean',
|
12
|
+
fields: [],
|
13
|
+
actions: [
|
14
|
+
ForestLiana::Model::Action.new({
|
15
|
+
name: 'Test',
|
16
|
+
endpoint: 'forest/actions/Test',
|
17
|
+
http_method: 'POST'
|
18
|
+
})
|
19
|
+
]
|
20
|
+
})
|
21
|
+
]
|
22
|
+
}
|
23
|
+
let(:scope_permissions) { nil }
|
24
|
+
let(:default_api_permissions) {
|
25
|
+
{
|
26
|
+
"data" => {
|
27
|
+
'collections' => {
|
28
|
+
"all_rights_collection_boolean" => {
|
29
|
+
"collection" => {
|
30
|
+
"browseEnabled" => true,
|
31
|
+
"readEnabled" => true,
|
32
|
+
"editEnabled" => true,
|
33
|
+
"addEnabled" => true,
|
34
|
+
"deleteEnabled" => true,
|
35
|
+
"exportEnabled" => true
|
36
|
+
},
|
37
|
+
"actions" => {
|
38
|
+
"Test" => {
|
39
|
+
"triggerEnabled" => true
|
40
|
+
},
|
41
|
+
}
|
42
|
+
},
|
43
|
+
},
|
44
|
+
'renderings' => scope_permissions
|
45
|
+
},
|
46
|
+
"meta" => {
|
47
|
+
"rolesACLActivated" => true
|
48
|
+
},
|
49
|
+
"stats" => {
|
50
|
+
"queries" => [
|
51
|
+
'SELECT COUNT(*) AS value FROM products;',
|
52
|
+
'SELECT COUNT(*) AS value FROM sometings;'
|
53
|
+
],
|
54
|
+
"values" => [
|
55
|
+
{
|
56
|
+
"type" => "Value",
|
57
|
+
"collection" => "Product",
|
58
|
+
"aggregate" => "Count"
|
59
|
+
}
|
60
|
+
],
|
61
|
+
},
|
62
|
+
}
|
63
|
+
}
|
64
|
+
let(:default_rendering_id) { 1 }
|
65
|
+
|
66
|
+
before do
|
67
|
+
allow(ForestLiana).to receive(:apimap).and_return(schema)
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#is_authorized?' do
|
71
|
+
# Resource is only used to retrieve the collection name as it's stub it does not
|
72
|
+
# need to be defined
|
73
|
+
let(:fake_ressource) { nil }
|
74
|
+
let(:default_rendering_id) { nil }
|
75
|
+
let(:api_permissions) { default_api_permissions }
|
76
|
+
|
77
|
+
before do
|
78
|
+
allow(ForestLiana::PermissionsGetter).to receive(:get_permissions_for_rendering).and_return(api_permissions)
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'when permissions liveQueries' do
|
82
|
+
context 'contains the query' do
|
83
|
+
request_info = {
|
84
|
+
"type" => "Value",
|
85
|
+
"collection" => "Product",
|
86
|
+
"aggregate" => "Count"
|
87
|
+
};
|
88
|
+
subject { described_class.new(fake_ressource, 'liveQueries', default_rendering_id, user_id: user_id, query_request_info: 'SELECT COUNT(*) AS value FROM sometings;') }
|
89
|
+
|
90
|
+
it 'should be authorized' do
|
91
|
+
expect(subject.is_authorized?).to be true
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'does not contains the query' do
|
96
|
+
subject { described_class.new(fake_ressource, 'liveQueries', default_rendering_id, user_id: user_id, query_request_info: 'SELECT * FROM products WHERE category = Gifts OR 1=1-- AND released = 1') }
|
97
|
+
it 'should NOT be authorized' do
|
98
|
+
expect(subject.is_authorized?).to be false
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when permissions statWithParameters' do
|
104
|
+
context 'contains the stat with the same parameters' do
|
105
|
+
request_info = {
|
106
|
+
"type" => "Value",
|
107
|
+
"collection" => "Product",
|
108
|
+
"aggregate" => "Count"
|
109
|
+
};
|
110
|
+
subject { described_class.new(fake_ressource, 'statWithParameters', default_rendering_id, user_id: user_id, query_request_info: request_info) }
|
111
|
+
|
112
|
+
it 'should be authorized' do
|
113
|
+
expect(subject.is_authorized?).to be true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'does not contains the stat with the same parameters' do
|
118
|
+
other_request_info = {
|
119
|
+
"type" => "Leaderboard",
|
120
|
+
"collection" => "Product",
|
121
|
+
"aggregate" => "Sum"
|
122
|
+
};
|
123
|
+
subject { described_class.new(fake_ressource, 'statWithParameters', default_rendering_id, user_id: user_id, query_request_info: other_request_info) }
|
124
|
+
it 'should NOT be authorized' do
|
125
|
+
expect(subject.is_authorized?).to be false
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forest_liana
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 6.0
|
4
|
+
version: 6.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sandro Munda
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-03-
|
11
|
+
date: 2021-03-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -364,11 +364,13 @@ files:
|
|
364
364
|
- spec/requests/actions_controller_spec.rb
|
365
365
|
- spec/requests/authentications_spec.rb
|
366
366
|
- spec/requests/resources_spec.rb
|
367
|
+
- spec/requests/stats_spec.rb
|
367
368
|
- spec/services/forest_liana/apimap_sorter_spec.rb
|
368
369
|
- spec/services/forest_liana/filters_parser_spec.rb
|
369
370
|
- spec/services/forest_liana/ip_whitelist_checker_spec.rb
|
370
371
|
- spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb
|
371
372
|
- spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb
|
373
|
+
- spec/services/forest_liana/permissions_checker_live_queries_spec.rb
|
372
374
|
- spec/services/forest_liana/permissions_formatter_spec.rb
|
373
375
|
- spec/services/forest_liana/permissions_getter_spec.rb
|
374
376
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
@@ -625,8 +627,10 @@ test_files:
|
|
625
627
|
- spec/requests/resources_spec.rb
|
626
628
|
- spec/requests/authentications_spec.rb
|
627
629
|
- spec/requests/actions_controller_spec.rb
|
630
|
+
- spec/requests/stats_spec.rb
|
628
631
|
- spec/spec_helper.rb
|
629
632
|
- spec/services/forest_liana/filters_parser_spec.rb
|
633
|
+
- spec/services/forest_liana/permissions_checker_live_queries_spec.rb
|
630
634
|
- spec/services/forest_liana/permissions_checker_acl_disabled_spec.rb
|
631
635
|
- spec/services/forest_liana/schema_adapter_spec.rb
|
632
636
|
- spec/services/forest_liana/permissions_checker_acl_enabled_spec.rb
|