praxis 2.0.pre.38 → 2.0.pre.40
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +9 -1
- data/gemfiles/.gitignore +3 -0
- data/gemfiles/active_6.gemfile +0 -1
- data/gemfiles/active_7.gemfile +4 -2
- data/lib/praxis/handlers/json.rb +5 -8
- data/lib/praxis/tasks/console.rb +11 -14
- data/lib/praxis/version.rb +1 -1
- data/spec/functional_cloud_spec.rb +168 -56
- data/spec/spec_helper.rb +2 -8
- data/tasks/thor/templates/generator/example_app/Gemfile +1 -2
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 19745c4d9f6ebe88d95a2118df3b7a41343a2076b2a17d051793fed535575b9d
|
4
|
+
data.tar.gz: af9eb1d60cf4367f0ceb0e7aadc589b7f7d862fa161815ccc05c46eaa08f8881
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a0a13bfeb6d1fd4512483fd4d341e1f606f1b391d0eda220019b5032f2593be4e79bd7061d375de2b97630eecd19feffa89db594d7c553c11163084b6bb00e1
|
7
|
+
data.tar.gz: e0ef5e1cc96d638496ef60f0ae4aaf82ccc2cecbf49cae5794642525283e201bfb600b4f02f4a85d1cd7b16988e6dc05a5a08162c7d9f894906017beba271de3
|
data/CHANGELOG.md
CHANGED
@@ -1,8 +1,16 @@
|
|
1
1
|
# Praxis Changelog
|
2
2
|
|
3
|
+
## 2.0.pre40
|
4
|
+
|
5
|
+
- Prevent IRB from running console in a new Thread (#405)
|
6
|
+
|
7
|
+
## 2.0.pre.39
|
8
|
+
|
9
|
+
- Revert JSON handler back to `json` gem, as `oj` was too unstable when used with ActiveSupport.
|
10
|
+
|
3
11
|
## 2.0.pre.38
|
4
12
|
|
5
|
-
-
|
13
|
+
- Stopped calling ::Oj.mimic_JSON in Praxis::Handlers::JSON. It breaks ActiveSupport::JSON's html escaping when called.
|
6
14
|
|
7
15
|
## 2.0.pre.37
|
8
16
|
|
data/gemfiles/.gitignore
ADDED
data/gemfiles/active_6.gemfile
CHANGED
data/gemfiles/active_7.gemfile
CHANGED
@@ -4,14 +4,16 @@
|
|
4
4
|
|
5
5
|
source 'https://rubygems.org'
|
6
6
|
|
7
|
-
|
7
|
+
# TODO: figure out incompatibility between ActiveRecord 7.1 and SQLite
|
8
|
+
# @see https://github.com/praxis/praxis/pull/405#issuecomment-1780570599
|
9
|
+
# Until then, we're pinned to 7.0.x for purposes of testing.
|
10
|
+
gem 'activerecord', '~> 7.0.8'
|
8
11
|
gem 'rubocop'
|
9
12
|
gem 'sequel', '~> 5'
|
10
13
|
|
11
14
|
group :test do
|
12
15
|
gem 'builder'
|
13
16
|
gem 'link_header'
|
14
|
-
gem 'oj'
|
15
17
|
gem 'parslet'
|
16
18
|
end
|
17
19
|
|
data/lib/praxis/handlers/json.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
1
|
module Praxis
|
4
2
|
module Handlers
|
5
3
|
class JSON
|
@@ -7,11 +5,11 @@ module Praxis
|
|
7
5
|
#
|
8
6
|
# @raise [Praxis::Exceptions::InvalidConfiguration] if the handler is unsupported
|
9
7
|
def initialize
|
10
|
-
require '
|
8
|
+
require 'json'
|
11
9
|
rescue LoadError
|
12
10
|
# Should never happen since JSON is a default gem; might as well be cautious!
|
13
11
|
raise Praxis::Exceptions::InvalidConfiguration,
|
14
|
-
|
12
|
+
"JSON handler depends on json ~> 1.0; please add it to your Gemfile"
|
15
13
|
end
|
16
14
|
|
17
15
|
# Parse a JSON document into structured data.
|
@@ -20,9 +18,8 @@ module Praxis
|
|
20
18
|
# @return [Hash,Array] the structured-data representation of the document
|
21
19
|
def parse(document)
|
22
20
|
# Try to be nice and accept an empty string as an empty payload (seems nice to do for dumb http clients)
|
23
|
-
return nil if document.nil? || document == ''
|
24
|
-
|
25
|
-
::Oj.load(document)
|
21
|
+
return nil if (document.nil? || document == '')
|
22
|
+
::JSON.parse(document)
|
26
23
|
end
|
27
24
|
|
28
25
|
# Generate a pretty-printed JSON document from structured data.
|
@@ -30,7 +27,7 @@ module Praxis
|
|
30
27
|
# @param [Hash,Array] structured_data
|
31
28
|
# @return [String]
|
32
29
|
def generate(structured_data)
|
33
|
-
::
|
30
|
+
::JSON.pretty_generate(structured_data)
|
34
31
|
end
|
35
32
|
end
|
36
33
|
end
|
data/lib/praxis/tasks/console.rb
CHANGED
@@ -5,38 +5,35 @@ namespace :praxis do
|
|
5
5
|
task :console do
|
6
6
|
# Use irb if available (which it almost always is).
|
7
7
|
require 'irb'
|
8
|
+
# Ensure that we save history just like normal IRB
|
9
|
+
require 'irb/ext/save-history'
|
8
10
|
|
9
11
|
Rake::Task['praxis:environment'].invoke
|
10
12
|
|
13
|
+
basedir = ::Praxis::Application.instance.root
|
14
|
+
nickname = File.basename(basedir)
|
15
|
+
|
11
16
|
# Keep IRB.setup from complaining about bad ARGV options
|
12
17
|
old_argv = ARGV.dup
|
13
18
|
ARGV.clear
|
14
|
-
IRB.setup
|
19
|
+
IRB.setup(basedir)
|
15
20
|
ARGV.concat(old_argv)
|
16
21
|
|
17
|
-
# Ensure that multi-irb has a context to work with (and, indirectly an instance of IRB::Irb).
|
18
|
-
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
19
|
-
|
20
|
-
# Allow reentrant IRB
|
21
|
-
require 'irb/ext/multi-irb'
|
22
|
-
|
23
|
-
# Ensure that we save history just like normal IRB
|
24
|
-
require 'irb/ext/save-history'
|
25
|
-
|
26
22
|
# Remove main object from prompt (its stringify is not useful)
|
27
|
-
nickname = File.basename(::Praxis::Application.instance.root)
|
28
23
|
IRB.conf[:PROMPT][:DEFAULT] = {
|
29
24
|
PROMPT_I: "%N(#{nickname}):%03n:%i> ",
|
30
25
|
PROMPT_N: "%N(#{nickname}):%03n:%i> ",
|
31
26
|
PROMPT_S: "%N(#{nickname}):%03n:%i%l ",
|
32
27
|
PROMPT_C: "%N(#{nickname}):%03n:%i* ",
|
33
28
|
}
|
34
|
-
|
35
29
|
# Disable inefficient, distracting autocomplete
|
36
30
|
IRB.conf[:USE_AUTOCOMPLETE] = false
|
37
31
|
|
38
|
-
# Invoke the REPL,
|
39
|
-
IRB.
|
32
|
+
# Invoke the REPL, setting the workspace binding to the application object.
|
33
|
+
IRB::Irb.new(IRB::WorkSpace.new(::Praxis::Application.instance)).run(
|
34
|
+
IRB.conf,
|
35
|
+
)
|
36
|
+
# Cleanly shut down to ensure we save history
|
40
37
|
IRB.irb_at_exit
|
41
38
|
end
|
42
39
|
end
|
data/lib/praxis/version.rb
CHANGED
@@ -12,30 +12,39 @@ describe 'Functional specs' do
|
|
12
12
|
context 'index' do
|
13
13
|
context 'with a valid request' do
|
14
14
|
it 'is successful' do
|
15
|
-
get '/api/clouds/1/instances?api_version=1.0',
|
15
|
+
get '/api/clouds/1/instances?api_version=1.0',
|
16
|
+
nil,
|
17
|
+
'global_session' => session
|
16
18
|
expect(last_response.headers['Content-Type']).to(
|
17
|
-
eq('application/vnd.acme.instance;type=collection')
|
19
|
+
eq('application/vnd.acme.instance;type=collection'),
|
18
20
|
)
|
19
21
|
end
|
20
22
|
end
|
21
23
|
|
22
24
|
context 'with a path param that can not load' do
|
23
25
|
it 'returns a useful error' do
|
24
|
-
get '/api/clouds/invalid/instances?api_version=1.0',
|
26
|
+
get '/api/clouds/invalid/instances?api_version=1.0',
|
27
|
+
nil,
|
28
|
+
'global_session' => session
|
25
29
|
|
26
30
|
expect(last_response.status).to eq 400
|
27
31
|
|
28
32
|
response = JSON.parse(last_response.body)
|
29
33
|
expect(response['name']).to eq 'ValidationError'
|
30
34
|
expect(response['summary']).to eq 'Error loading params.'
|
31
|
-
expect(response['errors']).to match_array(
|
35
|
+
expect(response['errors']).to match_array(
|
36
|
+
[/Error loading attribute \$\.params\.cloud_id/],
|
37
|
+
)
|
32
38
|
expect(response['cause']['name']).to eq 'ArgumentError'
|
33
39
|
end
|
34
40
|
end
|
35
41
|
|
36
42
|
context 'with a header that can not load' do
|
37
43
|
it 'returns a useful error' do
|
38
|
-
get '/api/clouds/1/instances?api_version=1.0',
|
44
|
+
get '/api/clouds/1/instances?api_version=1.0',
|
45
|
+
nil,
|
46
|
+
'global_session' => session,
|
47
|
+
'HTTP_ACCOUNT_ID' => 'invalid'
|
39
48
|
|
40
49
|
expect(last_response.status).to eq 400
|
41
50
|
|
@@ -43,14 +52,18 @@ describe 'Functional specs' do
|
|
43
52
|
|
44
53
|
expect(response['name']).to eq 'ValidationError'
|
45
54
|
expect(response['summary']).to eq 'Error loading headers.'
|
46
|
-
expect(response['errors']).to match_array(
|
55
|
+
expect(response['errors']).to match_array(
|
56
|
+
[/Error loading attribute .*Account-Id"/],
|
57
|
+
)
|
47
58
|
expect(response['cause']['name']).to eq 'ArgumentError'
|
48
59
|
end
|
49
60
|
end
|
50
61
|
|
51
62
|
context 'with a param that is invalid' do
|
52
63
|
it 'returns a useful error' do
|
53
|
-
get '/api/clouds/-1/instances?api_version=1.0',
|
64
|
+
get '/api/clouds/-1/instances?api_version=1.0',
|
65
|
+
nil,
|
66
|
+
'global_session' => session
|
54
67
|
|
55
68
|
expect(last_response.status).to eq 400
|
56
69
|
|
@@ -58,13 +71,18 @@ describe 'Functional specs' do
|
|
58
71
|
|
59
72
|
expect(response['name']).to eq 'ValidationError'
|
60
73
|
expect(response['summary']).to eq 'Error validating request data.'
|
61
|
-
expect(response['errors']).to match_array(
|
74
|
+
expect(response['errors']).to match_array(
|
75
|
+
[/.*cloud_id.*is smaller than the allowed min/],
|
76
|
+
)
|
62
77
|
end
|
63
78
|
end
|
64
79
|
|
65
80
|
context 'with a header that is invalid' do
|
66
81
|
it 'returns a useful error' do
|
67
|
-
get '/api/clouds/1/instances?api_version=1.0',
|
82
|
+
get '/api/clouds/1/instances?api_version=1.0',
|
83
|
+
nil,
|
84
|
+
'global_session' => session,
|
85
|
+
'HTTP_ACCOUNT_ID' => '-1'
|
68
86
|
|
69
87
|
expect(last_response.status).to eq 400
|
70
88
|
|
@@ -72,7 +90,9 @@ describe 'Functional specs' do
|
|
72
90
|
|
73
91
|
expect(response['name']).to eq 'ValidationError'
|
74
92
|
expect(response['summary']).to eq 'Error validating request data.'
|
75
|
-
expect(response['errors']).to match_array(
|
93
|
+
expect(response['errors']).to match_array(
|
94
|
+
[/.*headers.*Account-Id.*is smaller than the allowed min/],
|
95
|
+
)
|
76
96
|
end
|
77
97
|
end
|
78
98
|
|
@@ -87,7 +107,10 @@ describe 'Functional specs' do
|
|
87
107
|
end
|
88
108
|
|
89
109
|
it 'fails to validate the response' do
|
90
|
-
get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0',
|
110
|
+
get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0',
|
111
|
+
nil,
|
112
|
+
'HTTP_FOO' => 'bar',
|
113
|
+
'global_session' => session
|
91
114
|
expect(last_response.status).to eq(500)
|
92
115
|
response = JSON.parse(last_response.body)
|
93
116
|
|
@@ -97,16 +120,22 @@ describe 'Functional specs' do
|
|
97
120
|
end
|
98
121
|
|
99
122
|
context 'with response validation disabled' do
|
100
|
-
let(:praxis_config)
|
123
|
+
let(:praxis_config) do
|
124
|
+
double('praxis_config', validate_responses: false)
|
125
|
+
end
|
101
126
|
let(:config) { double('config', praxis: praxis_config) }
|
102
127
|
|
103
128
|
before do
|
104
|
-
expect(Praxis::Application.instance.config).to receive(
|
129
|
+
expect(Praxis::Application.instance.config).to receive(
|
130
|
+
:praxis,
|
131
|
+
).and_return(praxis_config)
|
105
132
|
end
|
106
133
|
|
107
134
|
it 'does not validate the response and succeeds' do
|
108
135
|
expect do
|
109
|
-
get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0',
|
136
|
+
get '/api/clouds/1/instances?response_content_type=somejunk&api_version=1.0',
|
137
|
+
nil,
|
138
|
+
'global_session' => session
|
110
139
|
end.to_not raise_error
|
111
140
|
end
|
112
141
|
end
|
@@ -114,10 +143,14 @@ describe 'Functional specs' do
|
|
114
143
|
context 'with a valid request but misusing request content-type' do
|
115
144
|
it 'is still successful and does not get confused about the sister post action' do
|
116
145
|
the_body = StringIO.new('') # This is a GET request passing a body
|
117
|
-
get '/api/clouds/1/instances?api_version=1.0',
|
146
|
+
get '/api/clouds/1/instances?api_version=1.0',
|
147
|
+
nil,
|
148
|
+
'rack.input' => the_body,
|
149
|
+
'CONTENT_TYPE' => 'application/json',
|
150
|
+
'global_session' => session
|
118
151
|
expect(last_response.status).to eq(200)
|
119
152
|
expect(last_response.headers['Content-Type']).to(
|
120
|
-
eq('application/vnd.acme.instance;type=collection')
|
153
|
+
eq('application/vnd.acme.instance;type=collection'),
|
121
154
|
)
|
122
155
|
end
|
123
156
|
end
|
@@ -126,30 +159,40 @@ describe 'Functional specs' do
|
|
126
159
|
context 'index using POST sister action' do
|
127
160
|
context 'with a valid request' do
|
128
161
|
it 'is successful and round trips the content type we pass in the body' do
|
129
|
-
payload = {
|
130
|
-
|
162
|
+
payload = {
|
163
|
+
response_content_type:
|
164
|
+
'application/vnd.acme.instance; type=collection; other=thing',
|
165
|
+
}
|
166
|
+
post '/api/clouds/1/instances/actions/index_using_post?api_version=1.0',
|
167
|
+
JSON.dump(payload),
|
168
|
+
'CONTENT_TYPE' => 'application/json',
|
169
|
+
'global_session' => session
|
131
170
|
expect(last_response.status).to eq(200)
|
132
171
|
expect(last_response.headers['Content-Type']).to(
|
133
|
-
eq(payload[:response_content_type])
|
172
|
+
eq(payload[:response_content_type]),
|
134
173
|
)
|
135
174
|
end
|
136
175
|
end
|
137
176
|
end
|
138
177
|
it 'works' do
|
139
178
|
the_body = StringIO.new('{}') # This is a funny, GET request expecting a body
|
140
|
-
get '/api/clouds/1/instances/2?junk=foo&api_version=1.0',
|
179
|
+
get '/api/clouds/1/instances/2?junk=foo&api_version=1.0',
|
180
|
+
nil,
|
181
|
+
'rack.input' => the_body,
|
182
|
+
'CONTENT_TYPE' => 'application/json',
|
183
|
+
'global_session' => session
|
141
184
|
expect(last_response.status).to eq(200)
|
142
185
|
expected = {
|
143
186
|
'cloud_id' => 1,
|
144
187
|
'id' => 2,
|
145
188
|
'junk' => 'foo',
|
146
189
|
'other_params' => {
|
147
|
-
'some_date' => '2012-12-21T00:00:00
|
148
|
-
'fail_filter' => false
|
190
|
+
'some_date' => '2012-12-21T00:00:00+00:00',
|
191
|
+
'fail_filter' => false,
|
149
192
|
},
|
150
193
|
'payload' => {
|
151
|
-
'optional' => 'not given'
|
152
|
-
}
|
194
|
+
'optional' => 'not given',
|
195
|
+
},
|
153
196
|
}
|
154
197
|
|
155
198
|
expect(JSON.parse(last_response.body)).to eq(expected)
|
@@ -160,13 +203,17 @@ describe 'Functional specs' do
|
|
160
203
|
end
|
161
204
|
|
162
205
|
it 'returns early when making the before filter break' do
|
163
|
-
get '/api/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true',
|
206
|
+
get '/api/clouds/1/instances/2?junk=foo&api_version=1.0&fail_filter=true',
|
207
|
+
nil,
|
208
|
+
'global_session' => session
|
164
209
|
expect(last_response.status).to eq(401)
|
165
210
|
end
|
166
211
|
|
167
212
|
context 'bulk_create multipart' do
|
168
213
|
let(:instance) { Instance.example }
|
169
|
-
let(:instance_json)
|
214
|
+
let(:instance_json) do
|
215
|
+
JSON.pretty_generate(instance.render(fields: { id: true, name: true }))
|
216
|
+
end
|
170
217
|
|
171
218
|
let(:form) do
|
172
219
|
form_data = MIME::Multipart::FormData.new
|
@@ -179,9 +226,13 @@ describe 'Functional specs' do
|
|
179
226
|
let(:body) { form.body.to_s }
|
180
227
|
|
181
228
|
it 'works' do
|
182
|
-
post '/api/clouds/1/instances?api_version=1.0',
|
229
|
+
post '/api/clouds/1/instances?api_version=1.0',
|
230
|
+
body,
|
231
|
+
'CONTENT_TYPE' => content_type,
|
232
|
+
'global_session' => session
|
183
233
|
|
184
|
-
_reponse_preamble, response =
|
234
|
+
_reponse_preamble, response =
|
235
|
+
Praxis::MultipartParser.parse(last_response.headers, last_response.body)
|
185
236
|
expect(response).to have(1).item
|
186
237
|
|
187
238
|
instance_part = response.first
|
@@ -194,7 +245,9 @@ describe 'Functional specs' do
|
|
194
245
|
|
195
246
|
response_instance = JSON.parse(instance_part.body)
|
196
247
|
expect(response_instance['key']).to eq(instance.id)
|
197
|
-
expect(response_instance['value'].values).to eq(
|
248
|
+
expect(response_instance['value'].values).to eq(
|
249
|
+
instance.render(fields: { id: true, name: true }).values,
|
250
|
+
)
|
198
251
|
end
|
199
252
|
end
|
200
253
|
|
@@ -216,7 +269,10 @@ describe 'Functional specs' do
|
|
216
269
|
|
217
270
|
context 'with a valid payload' do
|
218
271
|
before do
|
219
|
-
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
272
|
+
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
273
|
+
body,
|
274
|
+
'CONTENT_TYPE' => content_type,
|
275
|
+
'global_session' => session
|
220
276
|
end
|
221
277
|
|
222
278
|
subject(:response) { JSON.parse(last_response.body) }
|
@@ -244,11 +300,16 @@ describe 'Functional specs' do
|
|
244
300
|
let(:body) { form.body.to_s }
|
245
301
|
|
246
302
|
it 'returns an error' do
|
247
|
-
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
303
|
+
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
304
|
+
body,
|
305
|
+
'CONTENT_TYPE' => content_type,
|
306
|
+
'global_session' => session
|
248
307
|
response = JSON.parse(last_response.body)
|
249
308
|
|
250
309
|
expect(response['name']).to eq('ValidationError')
|
251
|
-
expect(response['errors']).to eq(
|
310
|
+
expect(response['errors']).to eq(
|
311
|
+
['Attribute $.payload.destination_path is required'],
|
312
|
+
)
|
252
313
|
end
|
253
314
|
end
|
254
315
|
|
@@ -273,9 +334,14 @@ describe 'Functional specs' do
|
|
273
334
|
subject(:response) { JSON.parse(last_response.body) }
|
274
335
|
|
275
336
|
before do
|
276
|
-
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
337
|
+
post '/api/clouds/1/instances/2/files?api_version=1.0',
|
338
|
+
body,
|
339
|
+
'CONTENT_TYPE' => content_type,
|
340
|
+
'global_session' => session
|
341
|
+
end
|
342
|
+
its(:keys) do
|
343
|
+
should eq(%w[destination_path name filename type contents options])
|
277
344
|
end
|
278
|
-
its(:keys) { should eq(%w[destination_path name filename type contents options]) }
|
279
345
|
its(['options']) { should eq({ 'extra_thing' => 'I am extra' }) }
|
280
346
|
end
|
281
347
|
end
|
@@ -283,11 +349,15 @@ describe 'Functional specs' do
|
|
283
349
|
context 'not found and API versions' do
|
284
350
|
context 'when no version is specified' do
|
285
351
|
it 'it tells you which available api versions would match' do
|
286
|
-
get '/api/clouds/1/instances/2?junk=foo',
|
352
|
+
get '/api/clouds/1/instances/2?junk=foo',
|
353
|
+
nil,
|
354
|
+
'global_session' => session
|
287
355
|
|
288
356
|
expect(last_response.status).to eq(404)
|
289
357
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
290
|
-
expect(last_response.body).to eq(
|
358
|
+
expect(last_response.body).to eq(
|
359
|
+
'NotFound. Your request did not specify an API version. Available versions = "1.0".',
|
360
|
+
)
|
291
361
|
end
|
292
362
|
it 'it just gives you a simple not found when nothing would have matched' do
|
293
363
|
get '/foobar?junk=foo', nil, 'global_session' => session
|
@@ -300,43 +370,58 @@ describe 'Functional specs' do
|
|
300
370
|
|
301
371
|
context 'when some version is specified, but wrong' do
|
302
372
|
it 'it tells you which possible correcte api versions exist' do
|
303
|
-
get '/api/clouds/1/instances/2?junk=foo&api_version=50.0',
|
373
|
+
get '/api/clouds/1/instances/2?junk=foo&api_version=50.0',
|
374
|
+
nil,
|
375
|
+
'global_session' => session
|
304
376
|
|
305
377
|
expect(last_response.status).to eq(404)
|
306
378
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
307
|
-
expect(last_response.body).to eq(
|
379
|
+
expect(last_response.body).to eq(
|
380
|
+
'NotFound. Your request specified API version = "50.0". Available versions = "1.0".',
|
381
|
+
)
|
308
382
|
end
|
309
383
|
end
|
310
384
|
end
|
311
385
|
|
312
386
|
context 'volumes' do
|
313
|
-
before
|
314
|
-
header 'X-Api-Version', '1.0'
|
315
|
-
end
|
387
|
+
before { header 'X-Api-Version', '1.0' }
|
316
388
|
|
317
389
|
context 'when no authorization header is passed' do
|
318
390
|
it 'works as expected' do
|
319
|
-
get '/api/clouds/1/volumes/123?junk=stuff',
|
391
|
+
get '/api/clouds/1/volumes/123?junk=stuff',
|
392
|
+
nil,
|
393
|
+
'global_session' => session
|
320
394
|
expect(last_response.status).to eq(200)
|
321
395
|
expect(Volume.load(last_response.body).validate).to be_empty
|
322
|
-
expect(last_response.headers['Content-Type']).to eq(
|
396
|
+
expect(last_response.headers['Content-Type']).to eq(
|
397
|
+
'application/vnd.acme.volume',
|
398
|
+
)
|
323
399
|
end
|
324
400
|
end
|
325
401
|
context 'when an authorization header is passed' do
|
326
402
|
it 'returns 401 when it does not match "secret" ' do
|
327
|
-
get '/api/clouds/1/volumes/123?junk=stuff',
|
403
|
+
get '/api/clouds/1/volumes/123?junk=stuff',
|
404
|
+
nil,
|
405
|
+
'HTTP_AUTHORIZATION' => 'foobar',
|
406
|
+
'global_session' => session
|
328
407
|
expect(last_response.status).to eq(401)
|
329
408
|
expect(last_response.body).to match(/Authentication info is invalid/)
|
330
409
|
end
|
331
410
|
it 'succeeds as expected when it matches "secret" ' do
|
332
|
-
get '/api/clouds/1/volumes/123?junk=stuff',
|
411
|
+
get '/api/clouds/1/volumes/123?junk=stuff',
|
412
|
+
nil,
|
413
|
+
'HTTP_AUTHORIZATION' => 'the secret',
|
414
|
+
'global_session' => session
|
333
415
|
expect(last_response.status).to eq(200)
|
334
416
|
end
|
335
417
|
end
|
336
418
|
|
337
419
|
context 'index action with no args defined' do
|
338
420
|
it 'dispatches successfully' do
|
339
|
-
get '/api/clouds/1/volumes',
|
421
|
+
get '/api/clouds/1/volumes',
|
422
|
+
nil,
|
423
|
+
'HTTP_AUTHORIZATION' => 'the secret',
|
424
|
+
'global_session' => session
|
340
425
|
expect(last_response.status).to eq(200)
|
341
426
|
end
|
342
427
|
end
|
@@ -345,22 +430,32 @@ describe 'Functional specs' do
|
|
345
430
|
context 'wildcard verb routing' do
|
346
431
|
let(:content_type) { 'application/json' }
|
347
432
|
it 'can terminate instances with POST' do
|
348
|
-
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
433
|
+
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
434
|
+
nil,
|
435
|
+
'CONTENT_TYPE' => content_type,
|
436
|
+
'global_session' => session
|
349
437
|
expect(last_response.status).to eq(200)
|
350
438
|
end
|
351
439
|
it 'can terminate instances with DELETE' do
|
352
|
-
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
440
|
+
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
441
|
+
nil,
|
442
|
+
'CONTENT_TYPE' => content_type,
|
443
|
+
'global_session' => session
|
353
444
|
expect(last_response.status).to eq(200)
|
354
445
|
end
|
355
446
|
end
|
356
447
|
|
357
448
|
context 'route options' do
|
358
449
|
it 'reach the endpoint that does not match the except clause' do
|
359
|
-
get '/api/clouds/23/otherinstances/_action/test?api_version=1.0',
|
450
|
+
get '/api/clouds/23/otherinstances/_action/test?api_version=1.0',
|
451
|
+
nil,
|
452
|
+
'global_session' => session
|
360
453
|
expect(last_response.status).to eq(200)
|
361
454
|
end
|
362
455
|
it 'does NOT reach the endpoint that matches the except clause' do
|
363
|
-
get '/api/clouds/23/otherinstances/_action/exceptional?api_version=1.0',
|
456
|
+
get '/api/clouds/23/otherinstances/_action/exceptional?api_version=1.0',
|
457
|
+
nil,
|
458
|
+
'global_session' => session
|
364
459
|
expect(last_response.status).to eq(404)
|
365
460
|
end
|
366
461
|
end
|
@@ -369,12 +464,17 @@ describe 'Functional specs' do
|
|
369
464
|
let(:content_type) { 'application/json' }
|
370
465
|
|
371
466
|
it 'can terminate' do
|
372
|
-
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
467
|
+
post '/api/clouds/23/instances/1/terminate?api_version=1.0',
|
468
|
+
nil,
|
469
|
+
'global_session' => session,
|
470
|
+
'CONTENT_TYPE' => content_type
|
373
471
|
expect(last_response.status).to eq(200)
|
374
472
|
end
|
375
473
|
|
376
474
|
it 'can not stop' do
|
377
|
-
post '/api/clouds/23/instances/1/stop?api_version=1.0',
|
475
|
+
post '/api/clouds/23/instances/1/stop?api_version=1.0',
|
476
|
+
'',
|
477
|
+
'global_session' => session
|
378
478
|
expect(last_response.status).to eq(403)
|
379
479
|
end
|
380
480
|
end
|
@@ -384,13 +484,18 @@ describe 'Functional specs' do
|
|
384
484
|
let(:content_type) { 'application/json' }
|
385
485
|
|
386
486
|
before do
|
387
|
-
post '/api/clouds/1/instances/2/terminate?api_version=1.0',
|
487
|
+
post '/api/clouds/1/instances/2/terminate?api_version=1.0',
|
488
|
+
body,
|
489
|
+
'CONTENT_TYPE' => content_type,
|
490
|
+
'global_session' => session
|
388
491
|
end
|
389
492
|
|
390
493
|
it 'returns a useful error message' do
|
391
494
|
body = JSON.parse(last_response.body)
|
392
495
|
expect(body['name']).to eq('ValidationError')
|
393
|
-
expect(body['summary']).to match(
|
496
|
+
expect(body['summary']).to match(
|
497
|
+
"Error loading payload. Used Content-Type: 'application/json'",
|
498
|
+
)
|
394
499
|
expect(body['errors']).to_not be_empty
|
395
500
|
end
|
396
501
|
end
|
@@ -400,7 +505,10 @@ describe 'Functional specs' do
|
|
400
505
|
let(:content_type) { 'application/json' }
|
401
506
|
|
402
507
|
before do
|
403
|
-
patch '/api/clouds/1/instances/3?api_version=1.0',
|
508
|
+
patch '/api/clouds/1/instances/3?api_version=1.0',
|
509
|
+
body,
|
510
|
+
'CONTENT_TYPE' => content_type,
|
511
|
+
'global_session' => session
|
404
512
|
end
|
405
513
|
|
406
514
|
subject(:response_body) { JSON.parse(last_response.body) }
|
@@ -429,7 +537,11 @@ describe 'Functional specs' do
|
|
429
537
|
|
430
538
|
its(['name']) { should eq 'ValidationError' }
|
431
539
|
its(['summary']) { should eq 'Error validating response' }
|
432
|
-
its(['errors'])
|
540
|
+
its(['errors']) do
|
541
|
+
should match_array [
|
542
|
+
/\$\.name value \(Invalid Name\) does not match regexp/,
|
543
|
+
]
|
544
|
+
end
|
433
545
|
|
434
546
|
it 'returns a validation error' do
|
435
547
|
expect(last_response.status).to eq(500)
|
data/spec/spec_helper.rb
CHANGED
@@ -25,17 +25,13 @@ require 'rack/test'
|
|
25
25
|
require 'rspec/its'
|
26
26
|
require 'rspec/collection_matchers'
|
27
27
|
|
28
|
-
require 'oj'
|
29
28
|
require 'json'
|
30
|
-
Oj.mimic_JSON
|
31
29
|
|
32
30
|
Dir["#{File.dirname(__FILE__)}/../lib/praxis/plugins/*.rb"].sort.each do |file|
|
33
31
|
require file
|
34
32
|
end
|
35
33
|
|
36
|
-
Dir["#{File.dirname(__FILE__)}/support/*.rb"].sort.each
|
37
|
-
require file
|
38
|
-
end
|
34
|
+
Dir["#{File.dirname(__FILE__)}/support/*.rb"].sort.each { |file| require file }
|
39
35
|
|
40
36
|
def suppress_output
|
41
37
|
original_stdout = $stdout.clone
|
@@ -57,9 +53,7 @@ RSpec.configure do |config|
|
|
57
53
|
end
|
58
54
|
|
59
55
|
config.before(:each) do
|
60
|
-
Praxis::Blueprint.cache = Hash.new
|
61
|
-
hash[key] = {}
|
62
|
-
end
|
56
|
+
Praxis::Blueprint.cache = Hash.new { |hash, key| hash[key] = {} }
|
63
57
|
end
|
64
58
|
|
65
59
|
config.before(:all) do
|
@@ -4,7 +4,6 @@ source 'https://rubygems.org'
|
|
4
4
|
|
5
5
|
gem 'activerecord'
|
6
6
|
gem 'link_header' # For pagination extensions
|
7
|
-
gem 'oj' # For fast JSON de/serialization handlers
|
8
7
|
gem 'parslet' # For field selection extension
|
9
8
|
gem 'praxis'
|
10
9
|
gem 'puma' # A much better web server than the default webrick
|
@@ -20,4 +19,4 @@ group :development, :test do
|
|
20
19
|
|
21
20
|
gem 'pry'
|
22
21
|
gem 'pry-byebug'
|
23
|
-
end
|
22
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: praxis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.pre.
|
4
|
+
version: 2.0.pre.40
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Josep M. Blanquer
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2023-
|
12
|
+
date: 2023-11-13 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|
@@ -387,6 +387,7 @@ files:
|
|
387
387
|
- Rakefile
|
388
388
|
- SELECTOR_NOTES.txt
|
389
389
|
- bin/praxis
|
390
|
+
- gemfiles/.gitignore
|
390
391
|
- gemfiles/active_6.gemfile
|
391
392
|
- gemfiles/active_7.gemfile
|
392
393
|
- lib/praxis.rb
|