praxis 2.0.pre.38 → 2.0.pre.40
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/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
|