haveapi 0.27.3 → 0.28.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/Gemfile +1 -1
- data/haveapi.gemspec +1 -1
- data/lib/haveapi/action.rb +125 -36
- data/lib/haveapi/actions/paginable.rb +3 -1
- data/lib/haveapi/authentication/basic/provider.rb +2 -0
- data/lib/haveapi/authentication/chain.rb +11 -7
- data/lib/haveapi/authentication/oauth2/config.rb +25 -3
- data/lib/haveapi/authentication/oauth2/provider.rb +92 -11
- data/lib/haveapi/authentication/oauth2/revoke_endpoint.rb +44 -3
- data/lib/haveapi/authentication/token/provider.rb +53 -15
- data/lib/haveapi/authorization.rb +42 -18
- data/lib/haveapi/client_examples/php_client.rb +1 -1
- data/lib/haveapi/client_examples/ruby_client.rb +1 -1
- data/lib/haveapi/context.rb +10 -4
- data/lib/haveapi/example.rb +15 -16
- data/lib/haveapi/extensions/action_exceptions.rb +6 -6
- data/lib/haveapi/model_adapters/active_record.rb +140 -68
- data/lib/haveapi/model_adapters/hash.rb +1 -1
- data/lib/haveapi/parameters/resource.rb +35 -3
- data/lib/haveapi/parameters/typed.rb +26 -7
- data/lib/haveapi/params.rb +27 -8
- data/lib/haveapi/resource.rb +4 -1
- data/lib/haveapi/resources/action_state.rb +8 -1
- data/lib/haveapi/route.rb +2 -2
- data/lib/haveapi/server.rb +137 -45
- data/lib/haveapi/validator.rb +2 -2
- data/lib/haveapi/validator_chain.rb +1 -0
- data/lib/haveapi/validators/confirmation.rb +1 -0
- data/lib/haveapi/validators/format.rb +6 -2
- data/lib/haveapi/validators/length.rb +2 -0
- data/lib/haveapi/validators/numericality.rb +2 -0
- data/lib/haveapi/validators/presence.rb +1 -1
- data/lib/haveapi/version.rb +1 -1
- data/lib/haveapi/views/version_page/client_auth.erb +1 -1
- data/lib/haveapi/views/version_page/client_example.erb +3 -3
- data/lib/haveapi/views/version_page/client_init.erb +1 -1
- data/lib/haveapi/views/version_page.erb +2 -2
- data/lib/haveapi/views/version_sidebar.erb +4 -2
- data/spec/action/authorize_spec.rb +99 -0
- data/spec/action/runtime_spec.rb +426 -0
- data/spec/action_state_spec.rb +52 -0
- data/spec/authentication/basic_spec.rb +29 -0
- data/spec/authentication/oauth2_spec.rb +329 -0
- data/spec/authentication/token_spec.rb +195 -0
- data/spec/authentication/token_version_routes_spec.rb +164 -0
- data/spec/authorization_spec.rb +66 -0
- data/spec/documentation/auth_filtering_spec.rb +195 -1
- data/spec/documentation/current_user_html_escaping_spec.rb +47 -0
- data/spec/documentation/examples_spec.rb +97 -0
- data/spec/documentation/host_html_escaping_spec.rb +41 -0
- data/spec/documentation_spec.rb +13 -0
- data/spec/extensions/action_exceptions_spec.rb +30 -0
- data/spec/model_adapters/active_record_spec.rb +406 -1
- data/spec/parameters/typed_spec.rb +42 -0
- data/spec/params_spec.rb +41 -0
- data/spec/server/integration_spec.rb +90 -0
- data/spec/validator_chain_spec.rb +39 -0
- data/spec/validators/confirmation_spec.rb +14 -0
- data/spec/validators/format_spec.rb +7 -0
- data/spec/validators/length_spec.rb +6 -0
- data/spec/validators/numericality_spec.rb +7 -0
- data/spec/validators/presence_spec.rb +2 -0
- data/test_support/client_test_api.rb +28 -0
- metadata +8 -4
- data/shell.nix +0 -20
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
module ServerIntegrationSpec
|
|
4
|
+
module State
|
|
5
|
+
class << self
|
|
6
|
+
def reset!
|
|
7
|
+
writes.clear
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def writes
|
|
11
|
+
@writes ||= []
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
3
17
|
describe HaveAPI::Server do
|
|
4
18
|
describe 'integration' do
|
|
5
19
|
api do
|
|
@@ -24,10 +38,38 @@ describe HaveAPI::Server do
|
|
|
24
38
|
end
|
|
25
39
|
end
|
|
26
40
|
end
|
|
41
|
+
|
|
42
|
+
define_resource(:Transfer) do
|
|
43
|
+
version 1
|
|
44
|
+
auth false
|
|
45
|
+
|
|
46
|
+
define_action(:Create) do
|
|
47
|
+
route ''
|
|
48
|
+
http_method :post
|
|
49
|
+
authorize { allow }
|
|
50
|
+
|
|
51
|
+
input(:hash) do
|
|
52
|
+
integer :amount, required: true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
output(:hash) do
|
|
56
|
+
bool :created
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def exec
|
|
60
|
+
ServerIntegrationSpec::State.writes << input[:amount]
|
|
61
|
+
{ created: true }
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
27
65
|
end
|
|
28
66
|
|
|
29
67
|
default_version 1
|
|
30
68
|
|
|
69
|
+
before do
|
|
70
|
+
ServerIntegrationSpec::State.reset!
|
|
71
|
+
end
|
|
72
|
+
|
|
31
73
|
it 'returns 406 for unsupported Accept' do
|
|
32
74
|
header 'Accept', 'text/plain'
|
|
33
75
|
options '/v1/'
|
|
@@ -45,6 +87,54 @@ describe HaveAPI::Server do
|
|
|
45
87
|
expect(api_response.message).to match(/Bad JSON syntax/)
|
|
46
88
|
end
|
|
47
89
|
|
|
90
|
+
it 'returns 400 for non-object JSON bodies' do
|
|
91
|
+
header 'Content-Type', 'application/json'
|
|
92
|
+
header 'Accept', 'application/json'
|
|
93
|
+
|
|
94
|
+
['[]', '"msg"', '123', 'true', 'null'].each do |body|
|
|
95
|
+
post '/v1/tests/echo', body
|
|
96
|
+
|
|
97
|
+
expect(last_response.status).to eq(400)
|
|
98
|
+
expect(api_response).not_to be_ok
|
|
99
|
+
expect(api_response.message).to eq('JSON body must be an object')
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'does not accept query-string input for non-GET actions' do
|
|
104
|
+
header 'Accept', 'application/json'
|
|
105
|
+
post '/v1/transfers?transfer[amount]=250', nil, {
|
|
106
|
+
'CONTENT_TYPE' => 'application/x-www-form-urlencoded'
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
expect(last_response.status).to eq(400)
|
|
110
|
+
expect(api_response).not_to be_ok
|
|
111
|
+
expect(ServerIntegrationSpec::State.writes).to be_empty
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'rejects non-JSON content types for JSON action bodies' do
|
|
115
|
+
header 'Accept', 'application/json'
|
|
116
|
+
post '/v1/transfers', '{"transfer":{"amount":250}}', {
|
|
117
|
+
'CONTENT_TYPE' => 'text/plain'
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
expect(last_response.status).to eq(415)
|
|
121
|
+
expect(api_response).not_to be_ok
|
|
122
|
+
expect(api_response.message).to eq('Unsupported Content-Type')
|
|
123
|
+
expect(ServerIntegrationSpec::State.writes).to be_empty
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'returns 400 for malformed Accept headers' do
|
|
127
|
+
invalid_accept = (+"\xFF").force_encoding(Encoding::UTF_8)
|
|
128
|
+
header 'Accept', invalid_accept
|
|
129
|
+
header 'Content-Type', 'application/json'
|
|
130
|
+
|
|
131
|
+
post '/v1/tests/echo', '{}'
|
|
132
|
+
|
|
133
|
+
expect(last_response.status).to eq(400)
|
|
134
|
+
expect(api_response).not_to be_ok
|
|
135
|
+
expect(api_response.message).to eq('Bad Accept header')
|
|
136
|
+
end
|
|
137
|
+
|
|
48
138
|
it 'returns JSON envelope for unknown route' do
|
|
49
139
|
header 'Accept', 'application/json'
|
|
50
140
|
get '/does-not-exist'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module HaveAPI::Validators
|
|
4
|
+
class CloneProbe < HaveAPI::Validator
|
|
5
|
+
name :clone_probe
|
|
6
|
+
takes :clone_probe
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_accessor :seen
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
self.seen = []
|
|
13
|
+
|
|
14
|
+
def setup
|
|
15
|
+
@message = 'invalid'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def describe
|
|
19
|
+
{}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def valid?(_v)
|
|
23
|
+
self.class.seen << object_id
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
describe HaveAPI::ValidatorChain do
|
|
30
|
+
it 'validates with per-call validator clones' do
|
|
31
|
+
chain = described_class.new(clone_probe: true)
|
|
32
|
+
original = chain.instance_variable_get(:@validators).first
|
|
33
|
+
|
|
34
|
+
HaveAPI::Validators::CloneProbe.seen.clear
|
|
35
|
+
expect(chain.validate('value', {})).to be true
|
|
36
|
+
|
|
37
|
+
expect(HaveAPI::Validators::CloneProbe.seen).not_to include(original.object_id)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -41,4 +41,18 @@ describe HaveAPI::Validators::Confirmation do
|
|
|
41
41
|
expect(validator.validate('bar', { other_param: 'foo' })).to be true
|
|
42
42
|
end
|
|
43
43
|
end
|
|
44
|
+
|
|
45
|
+
context 'with a string parameter reference' do
|
|
46
|
+
let(:validator) do
|
|
47
|
+
described_class.new(:confirm, {
|
|
48
|
+
param: 'other_param',
|
|
49
|
+
equal: false
|
|
50
|
+
})
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'looks up normalized input keys' do
|
|
54
|
+
expect(validator.validate('foo', { other_param: 'foo' })).to be false
|
|
55
|
+
expect(validator.validate('bar', { other_param: 'foo' })).to be true
|
|
56
|
+
end
|
|
57
|
+
end
|
|
44
58
|
end
|
|
@@ -49,4 +49,11 @@ describe HaveAPI::Validators::Format do
|
|
|
49
49
|
expect(validator.valid?('b')).to be true
|
|
50
50
|
end
|
|
51
51
|
end
|
|
52
|
+
|
|
53
|
+
it 'rejects values that cannot be converted to strings' do
|
|
54
|
+
validator = described_class.new(:format, /\A[a-z]+\z/)
|
|
55
|
+
|
|
56
|
+
expect(validator.valid?(['abc'])).to be false
|
|
57
|
+
expect(validator.valid?({ value: 'abc' })).to be false
|
|
58
|
+
end
|
|
52
59
|
end
|
|
@@ -42,4 +42,10 @@ describe HaveAPI::Validators::Length do
|
|
|
42
42
|
expect(v.valid?('a' * 4)).to be true
|
|
43
43
|
expect(v.valid?('a' * 5)).to be false
|
|
44
44
|
end
|
|
45
|
+
|
|
46
|
+
it 'rejects values without length' do
|
|
47
|
+
validator = described_class.new(:length, { min: 2 })
|
|
48
|
+
|
|
49
|
+
expect(validator.valid?(1)).to be false
|
|
50
|
+
end
|
|
45
51
|
end
|
|
@@ -77,4 +77,11 @@ describe HaveAPI::Validators::Numericality do
|
|
|
77
77
|
v = described_class.new(:number, { min: 5 })
|
|
78
78
|
expect(v.valid?('abc')).to be false
|
|
79
79
|
end
|
|
80
|
+
|
|
81
|
+
it 'rejects non-numeric values before numeric operations' do
|
|
82
|
+
validator = described_class.new(:number, { odd: true })
|
|
83
|
+
|
|
84
|
+
expect(validator.valid?(['5'])).to be false
|
|
85
|
+
expect(validator.valid?({ value: 5 })).to be false
|
|
86
|
+
end
|
|
80
87
|
end
|
|
@@ -8,6 +8,8 @@ describe HaveAPI::Validators::Presence do
|
|
|
8
8
|
expect(validator.valid?(nil)).to be false
|
|
9
9
|
expect(validator.valid?('')).to be false
|
|
10
10
|
expect(validator.valid?(" \t" * 4)).to be false
|
|
11
|
+
expect(validator.valid?([])).to be false
|
|
12
|
+
expect(validator.valid?({})).to be false
|
|
11
13
|
end
|
|
12
14
|
end
|
|
13
15
|
|
|
@@ -270,6 +270,7 @@ module HaveAPI
|
|
|
270
270
|
|
|
271
271
|
define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
272
272
|
extend DocFilter
|
|
273
|
+
|
|
273
274
|
resolve { |obj| obj[:id] }
|
|
274
275
|
output(:object_list) { use :all }
|
|
275
276
|
authorize { allow }
|
|
@@ -285,6 +286,7 @@ module HaveAPI
|
|
|
285
286
|
|
|
286
287
|
define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
287
288
|
extend DocFilter
|
|
289
|
+
|
|
288
290
|
resolve { |obj| obj[:id] }
|
|
289
291
|
output(:object) { use :all }
|
|
290
292
|
authorize { allow }
|
|
@@ -298,6 +300,7 @@ module HaveAPI
|
|
|
298
300
|
|
|
299
301
|
define_action(:Create, superclass: HaveAPI::Actions::Default::Create) do
|
|
300
302
|
extend DocFilter
|
|
303
|
+
|
|
301
304
|
resolve { |obj| obj[:id] }
|
|
302
305
|
input(:hash) do
|
|
303
306
|
string :name, required: true
|
|
@@ -324,6 +327,7 @@ module HaveAPI
|
|
|
324
327
|
|
|
325
328
|
define_action(:Index, superclass: HaveAPI::Actions::Default::Index) do
|
|
326
329
|
extend DocFilter
|
|
330
|
+
|
|
327
331
|
resolve { |obj| [obj[:project_id], obj[:id]] }
|
|
328
332
|
output(:object_list) { use :all }
|
|
329
333
|
authorize { allow }
|
|
@@ -339,6 +343,7 @@ module HaveAPI
|
|
|
339
343
|
|
|
340
344
|
define_action(:Show, superclass: HaveAPI::Actions::Default::Show) do
|
|
341
345
|
extend DocFilter
|
|
346
|
+
|
|
342
347
|
resolve { |obj| [obj[:project_id], obj[:id]] }
|
|
343
348
|
output(:object) { use :all }
|
|
344
349
|
authorize { allow }
|
|
@@ -352,6 +357,7 @@ module HaveAPI
|
|
|
352
357
|
|
|
353
358
|
define_action(:Create, superclass: HaveAPI::Actions::Default::Create) do
|
|
354
359
|
extend DocFilter
|
|
360
|
+
|
|
355
361
|
resolve { |obj| [obj[:project_id], obj[:id]] }
|
|
356
362
|
input(:hash) do
|
|
357
363
|
string :label, required: true
|
|
@@ -371,6 +377,7 @@ module HaveAPI
|
|
|
371
377
|
|
|
372
378
|
define_action(:Update, superclass: HaveAPI::Actions::Default::Update) do
|
|
373
379
|
extend DocFilter
|
|
380
|
+
|
|
374
381
|
resolve { |obj| [obj[:project_id], obj[:id]] }
|
|
375
382
|
input(:hash) do
|
|
376
383
|
bool :done
|
|
@@ -391,6 +398,7 @@ module HaveAPI
|
|
|
391
398
|
|
|
392
399
|
define_action(:Run) do
|
|
393
400
|
extend DocFilter
|
|
401
|
+
|
|
394
402
|
route '{task_id}/run'
|
|
395
403
|
http_method :post
|
|
396
404
|
blocking true
|
|
@@ -420,6 +428,7 @@ module HaveAPI
|
|
|
420
428
|
|
|
421
429
|
define_action(:Fail) do
|
|
422
430
|
extend DocFilter
|
|
431
|
+
|
|
423
432
|
route 'fail'
|
|
424
433
|
http_method :get
|
|
425
434
|
output(:hash) {}
|
|
@@ -430,8 +439,23 @@ module HaveAPI
|
|
|
430
439
|
end
|
|
431
440
|
end
|
|
432
441
|
|
|
442
|
+
define_action(:Slow) do
|
|
443
|
+
extend DocFilter
|
|
444
|
+
|
|
445
|
+
route 'slow'
|
|
446
|
+
http_method :get
|
|
447
|
+
output(:hash) {}
|
|
448
|
+
authorize { allow }
|
|
449
|
+
|
|
450
|
+
def exec
|
|
451
|
+
sleep 1
|
|
452
|
+
{}
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
|
|
433
456
|
define_action(:Echo) do
|
|
434
457
|
extend DocFilter
|
|
458
|
+
|
|
435
459
|
route 'echo'
|
|
436
460
|
http_method :post
|
|
437
461
|
input(:hash) do
|
|
@@ -459,6 +483,7 @@ module HaveAPI
|
|
|
459
483
|
|
|
460
484
|
define_action(:EchoOptional) do
|
|
461
485
|
extend DocFilter
|
|
486
|
+
|
|
462
487
|
route 'echo_optional'
|
|
463
488
|
http_method :post
|
|
464
489
|
input(:hash) do
|
|
@@ -483,6 +508,7 @@ module HaveAPI
|
|
|
483
508
|
|
|
484
509
|
define_action(:EchoOptionalGet) do
|
|
485
510
|
extend DocFilter
|
|
511
|
+
|
|
486
512
|
route 'echo_optional_get'
|
|
487
513
|
http_method :get
|
|
488
514
|
input(:hash) do
|
|
@@ -507,6 +533,7 @@ module HaveAPI
|
|
|
507
533
|
|
|
508
534
|
define_action(:EchoResource) do
|
|
509
535
|
extend DocFilter
|
|
536
|
+
|
|
510
537
|
route 'echo_resource'
|
|
511
538
|
http_method :post
|
|
512
539
|
input(:hash) do
|
|
@@ -524,6 +551,7 @@ module HaveAPI
|
|
|
524
551
|
|
|
525
552
|
define_action(:EchoResourceOptional) do
|
|
526
553
|
extend DocFilter
|
|
554
|
+
|
|
527
555
|
route 'echo_resource_optional'
|
|
528
556
|
http_method :get
|
|
529
557
|
input(:hash) do
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: haveapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.28.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jakub Skokan
|
|
@@ -29,14 +29,14 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: 0.
|
|
32
|
+
version: 0.28.0
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
35
|
version_requirements: !ruby/object:Gem::Requirement
|
|
36
36
|
requirements:
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
|
-
version: 0.
|
|
39
|
+
version: 0.28.0
|
|
40
40
|
- !ruby/object:Gem::Dependency
|
|
41
41
|
name: json
|
|
42
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -293,7 +293,6 @@ files:
|
|
|
293
293
|
- lib/haveapi/views/version_sidebar.erb
|
|
294
294
|
- lib/haveapi/views/version_sidebar/auth_nav.erb
|
|
295
295
|
- lib/haveapi/views/version_sidebar/resource_nav.erb
|
|
296
|
-
- shell.nix
|
|
297
296
|
- spec/.rubocop.yml
|
|
298
297
|
- spec/action/authorize_spec.rb
|
|
299
298
|
- spec/action/dsl_spec.rb
|
|
@@ -302,9 +301,13 @@ files:
|
|
|
302
301
|
- spec/authentication/basic_spec.rb
|
|
303
302
|
- spec/authentication/oauth2_spec.rb
|
|
304
303
|
- spec/authentication/token_spec.rb
|
|
304
|
+
- spec/authentication/token_version_routes_spec.rb
|
|
305
305
|
- spec/authorization_spec.rb
|
|
306
306
|
- spec/common_spec.rb
|
|
307
307
|
- spec/documentation/auth_filtering_spec.rb
|
|
308
|
+
- spec/documentation/current_user_html_escaping_spec.rb
|
|
309
|
+
- spec/documentation/examples_spec.rb
|
|
310
|
+
- spec/documentation/host_html_escaping_spec.rb
|
|
308
311
|
- spec/documentation_spec.rb
|
|
309
312
|
- spec/envelope_spec.rb
|
|
310
313
|
- spec/extensions/action_exceptions_spec.rb
|
|
@@ -315,6 +318,7 @@ files:
|
|
|
315
318
|
- spec/resource_spec.rb
|
|
316
319
|
- spec/server/integration_spec.rb
|
|
317
320
|
- spec/spec_helper.rb
|
|
321
|
+
- spec/validator_chain_spec.rb
|
|
318
322
|
- spec/validators/acceptance_spec.rb
|
|
319
323
|
- spec/validators/confirmation_spec.rb
|
|
320
324
|
- spec/validators/custom_spec.rb
|
data/shell.nix
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
let
|
|
2
|
-
pkgs = import <nixpkgs> {};
|
|
3
|
-
stdenv = pkgs.stdenv;
|
|
4
|
-
|
|
5
|
-
in stdenv.mkDerivation rec {
|
|
6
|
-
name = "haveapi";
|
|
7
|
-
|
|
8
|
-
buildInputs = with pkgs;[
|
|
9
|
-
ruby_3_3
|
|
10
|
-
git
|
|
11
|
-
openssl
|
|
12
|
-
];
|
|
13
|
-
|
|
14
|
-
shellHook = ''
|
|
15
|
-
export GEM_HOME=$(pwd)/../../.gems
|
|
16
|
-
export PATH="$GEM_HOME/bin:$PATH"
|
|
17
|
-
gem install bundler
|
|
18
|
-
bundle install
|
|
19
|
-
'';
|
|
20
|
-
}
|