grape 1.5.3 → 1.7.1
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 +92 -0
- data/CONTRIBUTING.md +32 -1
- data/README.md +176 -25
- data/UPGRADING.md +61 -4
- data/grape.gemspec +6 -6
- data/lib/grape/api/instance.rb +14 -18
- data/lib/grape/api.rb +17 -12
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dry_types.rb +12 -0
- data/lib/grape/dsl/api.rb +0 -2
- data/lib/grape/dsl/callbacks.rb +0 -2
- data/lib/grape/dsl/configuration.rb +0 -2
- data/lib/grape/dsl/desc.rb +4 -20
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +7 -7
- data/lib/grape/dsl/inside_route.rb +43 -30
- data/lib/grape/dsl/middleware.rb +4 -6
- data/lib/grape/dsl/parameters.rb +13 -10
- data/lib/grape/dsl/request_response.rb +9 -8
- data/lib/grape/dsl/routing.rb +6 -4
- data/lib/grape/dsl/settings.rb +5 -7
- data/lib/grape/dsl/validations.rb +0 -15
- data/lib/grape/endpoint.rb +22 -37
- data/lib/grape/error_formatter/json.rb +9 -7
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/base.rb +3 -2
- data/lib/grape/exceptions/missing_group_type.rb +8 -1
- data/lib/grape/exceptions/too_many_multipart_files.rb +11 -0
- data/lib/grape/exceptions/unsupported_group_type.rb +8 -1
- data/lib/grape/exceptions/validation.rb +1 -6
- data/lib/grape/formatter/json.rb +1 -0
- data/lib/grape/formatter/serializable_hash.rb +2 -1
- data/lib/grape/formatter/xml.rb +1 -0
- data/lib/grape/locale/en.yml +9 -8
- data/lib/grape/middleware/auth/dsl.rb +7 -2
- data/lib/grape/middleware/base.rb +3 -1
- data/lib/grape/middleware/error.rb +2 -2
- data/lib/grape/middleware/formatter.rb +4 -4
- data/lib/grape/middleware/stack.rb +3 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/path.rb +1 -0
- data/lib/grape/request.rb +4 -1
- data/lib/grape/router/attribute_translator.rb +1 -1
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +6 -0
- data/lib/grape/types/invalid_value.rb +8 -0
- data/lib/grape/util/cache.rb +1 -1
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/json.rb +2 -0
- data/lib/grape/util/lazy_value.rb +3 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_doc.rb +58 -0
- data/lib/grape/validations/params_scope.rb +138 -79
- data/lib/grape/validations/types/array_coercer.rb +0 -2
- data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
- data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
- data/lib/grape/validations/types/invalid_value.rb +0 -7
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +16 -8
- data/lib/grape/validations/types/set_coercer.rb +0 -2
- data/lib/grape/validations/types.rb +98 -30
- data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
- data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
- data/lib/grape/validations/validators/as_validator.rb +14 -0
- data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
- data/lib/grape/validations/validators/base.rb +82 -70
- data/lib/grape/validations/validators/coerce_validator.rb +75 -0
- data/lib/grape/validations/validators/default_validator.rb +51 -0
- data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
- data/lib/grape/validations/validators/except_values_validator.rb +24 -0
- data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
- data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
- data/lib/grape/validations/validators/presence_validator.rb +15 -0
- data/lib/grape/validations/validators/regexp_validator.rb +16 -0
- data/lib/grape/validations/validators/same_as_validator.rb +29 -0
- data/lib/grape/validations/validators/values_validator.rb +88 -0
- data/lib/grape/validations.rb +16 -6
- data/lib/grape/version.rb +1 -1
- data/lib/grape.rb +77 -29
- data/spec/grape/api/custom_validations_spec.rb +116 -45
- data/spec/grape/api/deeply_included_options_spec.rb +3 -5
- data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
- data/spec/grape/api/documentation_spec.rb +59 -0
- data/spec/grape/api/inherited_helpers_spec.rb +0 -2
- data/spec/grape/api/instance_spec.rb +0 -1
- data/spec/grape/api/invalid_format_spec.rb +2 -2
- data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/nested_helpers_spec.rb +0 -2
- data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/parameters_modification_spec.rb +0 -2
- data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
- data/spec/grape/api/recognize_path_spec.rb +1 -3
- data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
- data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
- data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
- data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
- data/spec/grape/api/shared_helpers_spec.rb +0 -2
- data/spec/grape/api_remount_spec.rb +16 -16
- data/spec/grape/api_spec.rb +462 -251
- data/spec/grape/config_spec.rb +0 -2
- data/spec/grape/dsl/callbacks_spec.rb +2 -3
- data/spec/grape/dsl/desc_spec.rb +2 -2
- data/spec/grape/dsl/headers_spec.rb +39 -11
- data/spec/grape/dsl/helpers_spec.rb +3 -4
- data/spec/grape/dsl/inside_route_spec.rb +16 -16
- data/spec/grape/dsl/logger_spec.rb +15 -19
- data/spec/grape/dsl/middleware_spec.rb +2 -3
- data/spec/grape/dsl/parameters_spec.rb +2 -2
- data/spec/grape/dsl/request_response_spec.rb +7 -8
- data/spec/grape/dsl/routing_spec.rb +11 -10
- data/spec/grape/dsl/settings_spec.rb +0 -2
- data/spec/grape/dsl/validations_spec.rb +0 -17
- data/spec/grape/endpoint/declared_spec.rb +261 -16
- data/spec/grape/endpoint_spec.rb +88 -59
- data/spec/grape/entity_spec.rb +22 -23
- data/spec/grape/exceptions/base_spec.rb +16 -2
- data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
- data/spec/grape/exceptions/invalid_accept_header_spec.rb +64 -24
- data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
- data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
- data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
- data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
- data/spec/grape/exceptions/missing_option_spec.rb +1 -3
- data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
- data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
- data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
- data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
- data/spec/grape/exceptions/validation_spec.rb +5 -5
- data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
- data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
- data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
- data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
- data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
- data/spec/grape/integration/rack_spec.rb +6 -7
- data/spec/grape/loading_spec.rb +8 -10
- data/spec/grape/middleware/auth/base_spec.rb +0 -1
- data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
- data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
- data/spec/grape/middleware/base_spec.rb +28 -19
- data/spec/grape/middleware/error_spec.rb +8 -3
- data/spec/grape/middleware/exception_spec.rb +111 -163
- data/spec/grape/middleware/formatter_spec.rb +33 -14
- data/spec/grape/middleware/globals_spec.rb +7 -6
- data/spec/grape/middleware/stack_spec.rb +14 -14
- data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
- data/spec/grape/middleware/versioner/header_spec.rb +30 -15
- data/spec/grape/middleware/versioner/param_spec.rb +7 -3
- data/spec/grape/middleware/versioner/path_spec.rb +5 -3
- data/spec/grape/middleware/versioner_spec.rb +1 -3
- data/spec/grape/named_api_spec.rb +0 -2
- data/spec/grape/parser_spec.rb +4 -2
- data/spec/grape/path_spec.rb +52 -54
- data/spec/grape/presenters/presenter_spec.rb +7 -8
- data/spec/grape/request_spec.rb +6 -6
- data/spec/grape/util/inheritable_setting_spec.rb +7 -8
- data/spec/grape/util/inheritable_values_spec.rb +3 -3
- data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
- data/spec/grape/util/stackable_values_spec.rb +7 -6
- data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
- data/spec/grape/validations/attributes_doc_spec.rb +153 -0
- data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
- data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
- data/spec/grape/validations/params_scope_spec.rb +361 -96
- data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
- data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
- data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
- data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
- data/spec/grape/validations/types_spec.rb +36 -10
- data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
- data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
- data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
- data/spec/grape/validations/validators/coerce_spec.rb +23 -24
- data/spec/grape/validations/validators/default_spec.rb +72 -80
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
- data/spec/grape/validations/validators/except_values_spec.rb +3 -5
- data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
- data/spec/grape/validations/validators/presence_spec.rb +16 -3
- data/spec/grape/validations/validators/regexp_spec.rb +25 -33
- data/spec/grape/validations/validators/same_as_spec.rb +14 -22
- data/spec/grape/validations/validators/values_spec.rb +201 -179
- data/spec/grape/validations_spec.rb +171 -79
- data/spec/integration/eager_load/eager_load_spec.rb +2 -2
- data/spec/integration/multi_json/json_spec.rb +1 -3
- data/spec/integration/multi_xml/xml_spec.rb +1 -3
- data/spec/shared/versioning_examples.rb +12 -9
- data/spec/spec_helper.rb +21 -6
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- metadata +41 -29
- data/lib/grape/validations/validators/all_or_none.rb +0 -15
- data/lib/grape/validations/validators/allow_blank.rb +0 -18
- data/lib/grape/validations/validators/as.rb +0 -16
- data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
- data/lib/grape/validations/validators/coerce.rb +0 -91
- data/lib/grape/validations/validators/default.rb +0 -48
- data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
- data/lib/grape/validations/validators/except_values.rb +0 -22
- data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
- data/lib/grape/validations/validators/presence.rb +0 -12
- data/lib/grape/validations/validators/regexp.rb +0 -13
- data/lib/grape/validations/validators/same_as.rb +0 -26
- data/lib/grape/validations/validators/values.rb +0 -83
- data/spec/grape/dsl/configuration_spec.rb +0 -16
- data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
- data/spec/support/eager_load.rb +0 -19
data/spec/grape/api_spec.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'spec_helper'
|
4
3
|
require 'shared/versioning_examples'
|
5
4
|
|
6
5
|
describe Grape::API do
|
7
|
-
subject
|
6
|
+
subject do
|
7
|
+
puts described_class
|
8
|
+
Class.new(described_class)
|
9
|
+
end
|
8
10
|
|
9
11
|
def app
|
10
12
|
subject
|
@@ -18,7 +20,7 @@ describe Grape::API do
|
|
18
20
|
end
|
19
21
|
|
20
22
|
get 'awesome/sauce/'
|
21
|
-
expect(last_response.status).to
|
23
|
+
expect(last_response.status).to be 200
|
22
24
|
expect(last_response.body).to eql 'Hello there.'
|
23
25
|
end
|
24
26
|
|
@@ -32,7 +34,7 @@ describe Grape::API do
|
|
32
34
|
expect(last_response.body).to eql 'Hello there.'
|
33
35
|
|
34
36
|
get '/hello'
|
35
|
-
expect(last_response.status).to
|
37
|
+
expect(last_response.status).to be 404
|
36
38
|
end
|
37
39
|
|
38
40
|
it 'supports OPTIONS' do
|
@@ -42,7 +44,7 @@ describe Grape::API do
|
|
42
44
|
end
|
43
45
|
|
44
46
|
options 'awesome/sauce'
|
45
|
-
expect(last_response.status).to
|
47
|
+
expect(last_response.status).to be 204
|
46
48
|
expect(last_response.body).to be_blank
|
47
49
|
end
|
48
50
|
|
@@ -51,7 +53,7 @@ describe Grape::API do
|
|
51
53
|
subject.get
|
52
54
|
|
53
55
|
post 'awesome/sauce'
|
54
|
-
expect(last_response.status).to
|
56
|
+
expect(last_response.status).to be 405
|
55
57
|
end
|
56
58
|
end
|
57
59
|
|
@@ -71,7 +73,7 @@ describe Grape::API do
|
|
71
73
|
end
|
72
74
|
|
73
75
|
describe '.version using path' do
|
74
|
-
|
76
|
+
it_behaves_like 'versioning' do
|
75
77
|
let(:macro_options) do
|
76
78
|
{
|
77
79
|
using: :path
|
@@ -81,7 +83,7 @@ describe Grape::API do
|
|
81
83
|
end
|
82
84
|
|
83
85
|
describe '.version using param' do
|
84
|
-
|
86
|
+
it_behaves_like 'versioning' do
|
85
87
|
let(:macro_options) do
|
86
88
|
{
|
87
89
|
using: :param,
|
@@ -92,7 +94,7 @@ describe Grape::API do
|
|
92
94
|
end
|
93
95
|
|
94
96
|
describe '.version using header' do
|
95
|
-
|
97
|
+
it_behaves_like 'versioning' do
|
96
98
|
let(:macro_options) do
|
97
99
|
{
|
98
100
|
using: :header,
|
@@ -101,26 +103,10 @@ describe Grape::API do
|
|
101
103
|
}
|
102
104
|
end
|
103
105
|
end
|
104
|
-
|
105
|
-
# Behavior as defined by rfc2616 when no header is defined
|
106
|
-
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
|
107
|
-
describe 'no specified accept header' do
|
108
|
-
# subject.version 'v1', using: :header
|
109
|
-
# subject.get '/hello' do
|
110
|
-
# 'hello'
|
111
|
-
# end
|
112
|
-
|
113
|
-
# it 'routes' do
|
114
|
-
# get '/hello'
|
115
|
-
# last_response.status.should eql 200
|
116
|
-
# end
|
117
|
-
end
|
118
|
-
|
119
|
-
# pending 'routes if any media type is allowed'
|
120
106
|
end
|
121
107
|
|
122
108
|
describe '.version using accept_version_header' do
|
123
|
-
|
109
|
+
it_behaves_like 'versioning' do
|
124
110
|
let(:macro_options) do
|
125
111
|
{
|
126
112
|
using: :accept_version_header
|
@@ -389,7 +375,7 @@ describe Grape::API do
|
|
389
375
|
end
|
390
376
|
end
|
391
377
|
|
392
|
-
before
|
378
|
+
before do
|
393
379
|
allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_json).and_return('abc')
|
394
380
|
allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_txt).and_return('def')
|
395
381
|
|
@@ -446,9 +432,10 @@ describe Grape::API do
|
|
446
432
|
expect(last_response.body).to eql 'hiya'
|
447
433
|
end
|
448
434
|
|
435
|
+
objects = ['string', :symbol, 1, -1.1, {}, [], true, false, nil].freeze
|
449
436
|
%i[put post].each do |verb|
|
450
|
-
context verb do
|
451
|
-
|
437
|
+
context verb.to_s do
|
438
|
+
objects.each do |object|
|
452
439
|
it "allows a(n) #{object.class} json object in params" do
|
453
440
|
subject.format :json
|
454
441
|
subject.send(verb) do
|
@@ -459,6 +446,7 @@ describe Grape::API do
|
|
459
446
|
expect(last_response.body).to eql ::Grape::Json.dump(object)
|
460
447
|
expect(last_request.params).to eql({})
|
461
448
|
end
|
449
|
+
|
462
450
|
it 'stores input in api.request.input' do
|
463
451
|
subject.format :json
|
464
452
|
subject.send(verb) do
|
@@ -468,6 +456,7 @@ describe Grape::API do
|
|
468
456
|
expect(last_response.status).to eq(verb == :post ? 201 : 200)
|
469
457
|
expect(last_response.body).to eql ::Grape::Json.dump(object).to_json
|
470
458
|
end
|
459
|
+
|
471
460
|
context 'chunked transfer encoding' do
|
472
461
|
it 'stores input in api.request.input' do
|
473
462
|
subject.format :json
|
@@ -562,7 +551,8 @@ describe Grape::API do
|
|
562
551
|
send(other_verb, '/example')
|
563
552
|
expected_rc = if other_verb == 'options' then 204
|
564
553
|
elsif other_verb == 'head' && verb == 'get' then 200
|
565
|
-
else
|
554
|
+
else
|
555
|
+
405
|
566
556
|
end
|
567
557
|
expect(last_response.status).to eql expected_rc
|
568
558
|
end
|
@@ -575,7 +565,7 @@ describe Grape::API do
|
|
575
565
|
end
|
576
566
|
|
577
567
|
post '/example'
|
578
|
-
expect(last_response.status).to
|
568
|
+
expect(last_response.status).to be 201
|
579
569
|
expect(last_response.body).to eql 'Created'
|
580
570
|
end
|
581
571
|
|
@@ -585,7 +575,7 @@ describe Grape::API do
|
|
585
575
|
'example'
|
586
576
|
end
|
587
577
|
put '/example'
|
588
|
-
expect(last_response.status).to
|
578
|
+
expect(last_response.status).to be 405
|
589
579
|
expect(last_response.body).to eql '405 Not Allowed'
|
590
580
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
591
581
|
end
|
@@ -593,15 +583,17 @@ describe Grape::API do
|
|
593
583
|
it 'runs only the before filter on 405 bad method' do
|
594
584
|
subject.namespace :example do
|
595
585
|
before { header 'X-Custom-Header', 'foo' }
|
586
|
+
|
596
587
|
before_validation { raise 'before_validation filter should not run' }
|
597
588
|
after_validation { raise 'after_validation filter should not run' }
|
598
589
|
after { raise 'after filter should not run' }
|
590
|
+
|
599
591
|
params { requires :only_for_get }
|
600
592
|
get
|
601
593
|
end
|
602
594
|
|
603
595
|
post '/example'
|
604
|
-
expect(last_response.status).to
|
596
|
+
expect(last_response.status).to be 405
|
605
597
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
606
598
|
end
|
607
599
|
|
@@ -610,29 +602,33 @@ describe Grape::API do
|
|
610
602
|
subject.namespace :example do
|
611
603
|
before do
|
612
604
|
raise 'before filter ran twice' if already_run
|
605
|
+
|
613
606
|
already_run = true
|
614
607
|
header 'X-Custom-Header', 'foo'
|
615
608
|
end
|
609
|
+
|
616
610
|
get
|
617
611
|
end
|
618
612
|
|
619
613
|
post '/example'
|
620
|
-
expect(last_response.status).to
|
614
|
+
expect(last_response.status).to be 405
|
621
615
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
622
616
|
end
|
623
617
|
|
624
618
|
it 'runs all filters and body with a custom OPTIONS method' do
|
625
619
|
subject.namespace :example do
|
626
620
|
before { header 'X-Custom-Header-1', 'foo' }
|
621
|
+
|
627
622
|
before_validation { header 'X-Custom-Header-2', 'foo' }
|
628
623
|
after_validation { header 'X-Custom-Header-3', 'foo' }
|
629
624
|
after { header 'X-Custom-Header-4', 'foo' }
|
625
|
+
|
630
626
|
options { 'yup' }
|
631
627
|
get
|
632
628
|
end
|
633
629
|
|
634
630
|
options '/example'
|
635
|
-
expect(last_response.status).to
|
631
|
+
expect(last_response.status).to be 200
|
636
632
|
expect(last_response.body).to eql 'yup'
|
637
633
|
expect(last_response.headers['Allow']).to be_nil
|
638
634
|
expect(last_response.headers['X-Custom-Header-1']).to eql 'foo'
|
@@ -649,13 +645,13 @@ describe Grape::API do
|
|
649
645
|
end
|
650
646
|
|
651
647
|
put '/example'
|
652
|
-
expect(last_response.status).to
|
653
|
-
expect(last_response.body).to eq
|
654
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
655
|
-
<error>
|
656
|
-
|
657
|
-
</error>
|
658
|
-
XML
|
648
|
+
expect(last_response.status).to be 405
|
649
|
+
expect(last_response.body).to eq <<~XML
|
650
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
651
|
+
<error>
|
652
|
+
<message>405 Not Allowed</message>
|
653
|
+
</error>
|
654
|
+
XML
|
659
655
|
end
|
660
656
|
end
|
661
657
|
|
@@ -669,7 +665,7 @@ XML
|
|
669
665
|
'example'
|
670
666
|
end
|
671
667
|
put '/example'
|
672
|
-
expect(last_response.status).to
|
668
|
+
expect(last_response.status).to be 405
|
673
669
|
expect(last_response.body).to eql '405 Not Allowed'
|
674
670
|
end
|
675
671
|
end
|
@@ -713,7 +709,7 @@ XML
|
|
713
709
|
end
|
714
710
|
|
715
711
|
it 'returns a 204' do
|
716
|
-
expect(last_response.status).to
|
712
|
+
expect(last_response.status).to be 204
|
717
713
|
end
|
718
714
|
|
719
715
|
it 'has an empty body' do
|
@@ -749,11 +745,53 @@ XML
|
|
749
745
|
end
|
750
746
|
end
|
751
747
|
|
748
|
+
describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
|
749
|
+
before do
|
750
|
+
subject.namespace :example do
|
751
|
+
get do
|
752
|
+
'example'
|
753
|
+
end
|
754
|
+
|
755
|
+
patch do
|
756
|
+
'example'
|
757
|
+
end
|
758
|
+
|
759
|
+
post do
|
760
|
+
'example'
|
761
|
+
end
|
762
|
+
|
763
|
+
delete do
|
764
|
+
'example'
|
765
|
+
end
|
766
|
+
|
767
|
+
put do
|
768
|
+
'example'
|
769
|
+
end
|
770
|
+
end
|
771
|
+
options '/example'
|
772
|
+
end
|
773
|
+
|
774
|
+
describe 'it adds an OPTIONS route for namespaced endpoints that' do
|
775
|
+
it 'returns a 204' do
|
776
|
+
expect(last_response.status).to be 204
|
777
|
+
end
|
778
|
+
|
779
|
+
it 'has an empty body' do
|
780
|
+
expect(last_response.body).to be_blank
|
781
|
+
end
|
782
|
+
|
783
|
+
it 'has an Allow header' do
|
784
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
|
785
|
+
end
|
786
|
+
end
|
787
|
+
end
|
788
|
+
|
752
789
|
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
753
790
|
before do
|
754
791
|
subject.before { header 'X-Custom-Header', 'foo' }
|
755
792
|
subject.namespace :example do
|
756
793
|
before { header 'X-Custom-Header-2', 'foo' }
|
794
|
+
|
757
795
|
get :inner do
|
758
796
|
'example/inner'
|
759
797
|
end
|
@@ -762,7 +800,7 @@ XML
|
|
762
800
|
end
|
763
801
|
|
764
802
|
it 'returns a 204' do
|
765
|
-
expect(last_response.status).to
|
803
|
+
expect(last_response.status).to be 204
|
766
804
|
end
|
767
805
|
|
768
806
|
it 'has an empty body' do
|
@@ -800,7 +838,7 @@ XML
|
|
800
838
|
end
|
801
839
|
|
802
840
|
it 'returns a 405' do
|
803
|
-
expect(last_response.status).to
|
841
|
+
expect(last_response.status).to be 405
|
804
842
|
end
|
805
843
|
|
806
844
|
it 'contains error message in body' do
|
@@ -816,7 +854,7 @@ XML
|
|
816
854
|
end
|
817
855
|
end
|
818
856
|
|
819
|
-
describe 'when hook behaviour is controlled by attributes on the route
|
857
|
+
describe 'when hook behaviour is controlled by attributes on the route' do
|
820
858
|
before do
|
821
859
|
subject.before do
|
822
860
|
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
|
@@ -839,28 +877,31 @@ XML
|
|
839
877
|
let(:response) { delete('/example') }
|
840
878
|
|
841
879
|
it 'responds with a 405 status' do
|
842
|
-
expect(response.status).to
|
880
|
+
expect(response.status).to be 405
|
843
881
|
end
|
844
882
|
end
|
845
883
|
|
846
884
|
context 'when HTTP method is defined with attribute' do
|
847
885
|
let(:response) { post('/example?secret=incorrect_password') }
|
886
|
+
|
848
887
|
it 'responds with the defined error in the before hook' do
|
849
|
-
expect(response.status).to
|
888
|
+
expect(response.status).to be 401
|
850
889
|
end
|
851
890
|
end
|
852
891
|
|
853
892
|
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
|
854
893
|
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
|
894
|
+
|
855
895
|
it 'ends up in the endpoint' do
|
856
|
-
expect(response.status).to
|
896
|
+
expect(response.status).to be 401
|
857
897
|
end
|
858
898
|
end
|
859
899
|
|
860
900
|
context 'when HTTP method is defined and everything is like the before hooks expect' do
|
861
901
|
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
|
902
|
+
|
862
903
|
it 'ends up in the endpoint' do
|
863
|
-
expect(response.status).to
|
904
|
+
expect(response.status).to be 201
|
864
905
|
end
|
865
906
|
end
|
866
907
|
|
@@ -868,7 +909,7 @@ XML
|
|
868
909
|
let(:response) { head('/example?id=504') }
|
869
910
|
|
870
911
|
it 'responds with 401 because before expectations in before hooks are not met' do
|
871
|
-
expect(response.status).to
|
912
|
+
expect(response.status).to be 401
|
872
913
|
end
|
873
914
|
end
|
874
915
|
|
@@ -876,7 +917,7 @@ XML
|
|
876
917
|
let(:response) { head('/example?id=504&secret=password') }
|
877
918
|
|
878
919
|
it 'responds with 200 because before hooks are not called' do
|
879
|
-
expect(response.status).to
|
920
|
+
expect(response.status).to be 200
|
880
921
|
end
|
881
922
|
end
|
882
923
|
end
|
@@ -893,7 +934,7 @@ XML
|
|
893
934
|
end
|
894
935
|
|
895
936
|
it 'returns a 200' do
|
896
|
-
expect(last_response.status).to
|
937
|
+
expect(last_response.status).to be 200
|
897
938
|
end
|
898
939
|
|
899
940
|
it 'has an empty body' do
|
@@ -909,31 +950,33 @@ XML
|
|
909
950
|
'example'
|
910
951
|
end
|
911
952
|
head '/example'
|
912
|
-
expect(last_response.status).to
|
953
|
+
expect(last_response.status).to be 400
|
913
954
|
end
|
914
955
|
end
|
915
956
|
|
916
957
|
context 'do_not_route_head!' do
|
917
|
-
before
|
958
|
+
before do
|
918
959
|
subject.do_not_route_head!
|
919
960
|
subject.get 'example' do
|
920
961
|
'example'
|
921
962
|
end
|
922
963
|
end
|
964
|
+
|
923
965
|
it 'options does not contain HEAD' do
|
924
966
|
options '/example'
|
925
|
-
expect(last_response.status).to
|
967
|
+
expect(last_response.status).to be 204
|
926
968
|
expect(last_response.body).to eql ''
|
927
969
|
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'
|
928
970
|
end
|
971
|
+
|
929
972
|
it 'does not allow HEAD on a GET request' do
|
930
973
|
head '/example'
|
931
|
-
expect(last_response.status).to
|
974
|
+
expect(last_response.status).to be 405
|
932
975
|
end
|
933
976
|
end
|
934
977
|
|
935
978
|
context 'do_not_route_options!' do
|
936
|
-
before
|
979
|
+
before do
|
937
980
|
subject.do_not_route_options!
|
938
981
|
subject.get 'example' do
|
939
982
|
'example'
|
@@ -942,19 +985,19 @@ XML
|
|
942
985
|
|
943
986
|
it 'does not create an OPTIONS route' do
|
944
987
|
options '/example'
|
945
|
-
expect(last_response.status).to
|
988
|
+
expect(last_response.status).to be 405
|
946
989
|
end
|
947
990
|
|
948
991
|
it 'does not include OPTIONS in Allow header' do
|
949
992
|
options '/example'
|
950
|
-
expect(last_response.status).to
|
993
|
+
expect(last_response.status).to be 405
|
951
994
|
expect(last_response.headers['Allow']).to eql 'GET, HEAD'
|
952
995
|
end
|
953
996
|
end
|
954
997
|
|
955
998
|
describe '.compile!' do
|
956
999
|
it 'requires the grape/eager_load file' do
|
957
|
-
expect(app).to receive(:require).with('grape/eager_load')
|
1000
|
+
expect(app).to receive(:require).with('grape/eager_load').and_return(nil)
|
958
1001
|
app.compile!
|
959
1002
|
end
|
960
1003
|
|
@@ -976,7 +1019,7 @@ XML
|
|
976
1019
|
context 'when the app was mounted' do
|
977
1020
|
it 'returns the first mounted instance' do
|
978
1021
|
mounted_app = app
|
979
|
-
Class.new(
|
1022
|
+
Class.new(described_class) do
|
980
1023
|
namespace 'new_namespace' do
|
981
1024
|
mount mounted_app
|
982
1025
|
end
|
@@ -1004,6 +1047,7 @@ XML
|
|
1004
1047
|
end
|
1005
1048
|
subject.namespace :blah do
|
1006
1049
|
before { @foo = 'foo' }
|
1050
|
+
|
1007
1051
|
get '/' do
|
1008
1052
|
"blah - #{@foo}"
|
1009
1053
|
end
|
@@ -1045,7 +1089,7 @@ XML
|
|
1045
1089
|
@var ||= 'default'
|
1046
1090
|
end
|
1047
1091
|
|
1048
|
-
expect(m).to receive(:do_something!).
|
1092
|
+
expect(m).to receive(:do_something!).twice
|
1049
1093
|
get '/'
|
1050
1094
|
expect(last_response.body).to eql 'default'
|
1051
1095
|
end
|
@@ -1061,21 +1105,23 @@ XML
|
|
1061
1105
|
end
|
1062
1106
|
subject.resource ':id' do
|
1063
1107
|
before { a.do_something! }
|
1108
|
+
|
1064
1109
|
before_validation { b.do_something! }
|
1065
1110
|
after_validation { c.do_something! }
|
1066
1111
|
after { d.do_something! }
|
1112
|
+
|
1067
1113
|
get do
|
1068
1114
|
'got it'
|
1069
1115
|
end
|
1070
1116
|
end
|
1071
1117
|
|
1072
|
-
expect(a).to receive(:do_something!).
|
1073
|
-
expect(b).to receive(:do_something!).
|
1074
|
-
expect(c).to receive(:do_something!).
|
1075
|
-
expect(d).to receive(:do_something!).
|
1118
|
+
expect(a).to receive(:do_something!).once
|
1119
|
+
expect(b).to receive(:do_something!).once
|
1120
|
+
expect(c).to receive(:do_something!).once
|
1121
|
+
expect(d).to receive(:do_something!).once
|
1076
1122
|
|
1077
1123
|
get '/123'
|
1078
|
-
expect(last_response.status).to
|
1124
|
+
expect(last_response.status).to be 200
|
1079
1125
|
expect(last_response.body).to eql 'got it'
|
1080
1126
|
end
|
1081
1127
|
|
@@ -1090,21 +1136,23 @@ XML
|
|
1090
1136
|
end
|
1091
1137
|
subject.resource ':id' do
|
1092
1138
|
before { a.do_something! }
|
1139
|
+
|
1093
1140
|
before_validation { b.do_something! }
|
1094
1141
|
after_validation { c.do_something! }
|
1095
1142
|
after { d.do_something! }
|
1143
|
+
|
1096
1144
|
get do
|
1097
1145
|
'got it'
|
1098
1146
|
end
|
1099
1147
|
end
|
1100
1148
|
|
1101
|
-
expect(a).to receive(:do_something!).
|
1102
|
-
expect(b).to receive(:do_something!).
|
1149
|
+
expect(a).to receive(:do_something!).once
|
1150
|
+
expect(b).to receive(:do_something!).once
|
1103
1151
|
expect(c).to receive(:do_something!).exactly(0).times
|
1104
1152
|
expect(d).to receive(:do_something!).exactly(0).times
|
1105
1153
|
|
1106
1154
|
get '/abc'
|
1107
|
-
expect(last_response.status).to
|
1155
|
+
expect(last_response.status).to be 400
|
1108
1156
|
expect(last_response.body).to eql 'id is invalid'
|
1109
1157
|
end
|
1110
1158
|
|
@@ -1120,21 +1168,23 @@ XML
|
|
1120
1168
|
end
|
1121
1169
|
subject.resource ':id' do
|
1122
1170
|
before { a.here(i += 1) }
|
1171
|
+
|
1123
1172
|
before_validation { b.here(i += 1) }
|
1124
1173
|
after_validation { c.here(i += 1) }
|
1125
1174
|
after { d.here(i += 1) }
|
1175
|
+
|
1126
1176
|
get do
|
1127
1177
|
'got it'
|
1128
1178
|
end
|
1129
1179
|
end
|
1130
1180
|
|
1131
|
-
expect(a).to receive(:here).with(1).
|
1132
|
-
expect(b).to receive(:here).with(2).
|
1133
|
-
expect(c).to receive(:here).with(3).
|
1134
|
-
expect(d).to receive(:here).with(4).
|
1181
|
+
expect(a).to receive(:here).with(1).once
|
1182
|
+
expect(b).to receive(:here).with(2).once
|
1183
|
+
expect(c).to receive(:here).with(3).once
|
1184
|
+
expect(d).to receive(:here).with(4).once
|
1135
1185
|
|
1136
1186
|
get '/123'
|
1137
|
-
expect(last_response.status).to
|
1187
|
+
expect(last_response.status).to be 200
|
1138
1188
|
expect(last_response.body).to eql 'got it'
|
1139
1189
|
end
|
1140
1190
|
end
|
@@ -1151,7 +1201,7 @@ XML
|
|
1151
1201
|
|
1152
1202
|
it 'does not set Cache-Control' do
|
1153
1203
|
get '/foo'
|
1154
|
-
expect(last_response.headers['Cache-Control']).to
|
1204
|
+
expect(last_response.headers['Cache-Control']).to be_nil
|
1155
1205
|
end
|
1156
1206
|
|
1157
1207
|
it 'sets content type for xml' do
|
@@ -1176,7 +1226,7 @@ XML
|
|
1176
1226
|
|
1177
1227
|
it 'returns raw data when content type binary' do
|
1178
1228
|
image_filename = 'grape.png'
|
1179
|
-
file = File.
|
1229
|
+
file = File.binread(image_filename)
|
1180
1230
|
subject.format :binary
|
1181
1231
|
subject.get('/binary_file') { File.binread(image_filename) }
|
1182
1232
|
get '/binary_file'
|
@@ -1208,7 +1258,7 @@ XML
|
|
1208
1258
|
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
|
1209
1259
|
|
1210
1260
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1211
|
-
expect(last_response.headers['Content-Length']).to
|
1261
|
+
expect(last_response.headers['Content-Length']).to be_nil
|
1212
1262
|
expect(last_response.headers['Cache-Control']).to eq('no-cache')
|
1213
1263
|
expect(last_response.headers['Transfer-Encoding']).to eq('chunked')
|
1214
1264
|
|
@@ -1225,7 +1275,7 @@ XML
|
|
1225
1275
|
subject.format :json
|
1226
1276
|
subject.get('/error') { error!('error in json', 500) }
|
1227
1277
|
get '/error.json'
|
1228
|
-
expect(last_response.status).to
|
1278
|
+
expect(last_response.status).to be 500
|
1229
1279
|
expect(last_response.headers['Content-Type']).to eql 'application/json'
|
1230
1280
|
end
|
1231
1281
|
|
@@ -1233,7 +1283,7 @@ XML
|
|
1233
1283
|
subject.format :xml
|
1234
1284
|
subject.get('/error') { error!('error in xml', 500) }
|
1235
1285
|
get '/error'
|
1236
|
-
expect(last_response.status).to
|
1286
|
+
expect(last_response.status).to be 500
|
1237
1287
|
expect(last_response.headers['Content-Type']).to eql 'application/xml'
|
1238
1288
|
end
|
1239
1289
|
|
@@ -1492,9 +1542,9 @@ XML
|
|
1492
1542
|
end
|
1493
1543
|
subject.get(:hello) { 'Hello, world.' }
|
1494
1544
|
get '/hello'
|
1495
|
-
expect(last_response.status).to
|
1545
|
+
expect(last_response.status).to be 401
|
1496
1546
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1497
|
-
expect(last_response.status).to
|
1547
|
+
expect(last_response.status).to be 200
|
1498
1548
|
end
|
1499
1549
|
|
1500
1550
|
it 'is scopable' do
|
@@ -1508,9 +1558,9 @@ XML
|
|
1508
1558
|
end
|
1509
1559
|
|
1510
1560
|
get '/hello'
|
1511
|
-
expect(last_response.status).to
|
1561
|
+
expect(last_response.status).to be 200
|
1512
1562
|
get '/admin/hello'
|
1513
|
-
expect(last_response.status).to
|
1563
|
+
expect(last_response.status).to be 401
|
1514
1564
|
end
|
1515
1565
|
|
1516
1566
|
it 'is callable via .auth as well' do
|
@@ -1520,9 +1570,9 @@ XML
|
|
1520
1570
|
|
1521
1571
|
subject.get(:hello) { 'Hello, world.' }
|
1522
1572
|
get '/hello'
|
1523
|
-
expect(last_response.status).to
|
1573
|
+
expect(last_response.status).to be 401
|
1524
1574
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1525
|
-
expect(last_response.status).to
|
1575
|
+
expect(last_response.status).to be 200
|
1526
1576
|
end
|
1527
1577
|
|
1528
1578
|
it 'has access to the current endpoint' do
|
@@ -1536,7 +1586,7 @@ XML
|
|
1536
1586
|
|
1537
1587
|
subject.get(:hello) { 'Hello, world.' }
|
1538
1588
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1539
|
-
expect(basic_auth_context).to
|
1589
|
+
expect(basic_auth_context).to be_a(Grape::Endpoint)
|
1540
1590
|
end
|
1541
1591
|
|
1542
1592
|
it 'has access to helper methods' do
|
@@ -1552,9 +1602,9 @@ XML
|
|
1552
1602
|
|
1553
1603
|
subject.get(:hello) { 'Hello, world.' }
|
1554
1604
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1555
|
-
expect(last_response.status).to
|
1605
|
+
expect(last_response.status).to be 200
|
1556
1606
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
|
1557
|
-
expect(last_response.status).to
|
1607
|
+
expect(last_response.status).to be 401
|
1558
1608
|
end
|
1559
1609
|
|
1560
1610
|
it 'can set instance variables accessible to routes' do
|
@@ -1566,44 +1616,36 @@ XML
|
|
1566
1616
|
|
1567
1617
|
subject.get(:hello) { @hello }
|
1568
1618
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1569
|
-
expect(last_response.status).to
|
1619
|
+
expect(last_response.status).to be 200
|
1570
1620
|
expect(last_response.body).to eql 'Hello, world.'
|
1571
1621
|
end
|
1572
1622
|
end
|
1573
1623
|
|
1574
1624
|
describe '.logger' do
|
1575
|
-
subject do
|
1576
|
-
Class.new(Grape::API) do
|
1577
|
-
def self.io
|
1578
|
-
@io ||= StringIO.new
|
1579
|
-
end
|
1580
|
-
logger ::Logger.new(io)
|
1581
|
-
end
|
1582
|
-
end
|
1583
|
-
|
1584
1625
|
it 'returns an instance of Logger class by default' do
|
1585
1626
|
expect(subject.logger.class).to eql Logger
|
1586
1627
|
end
|
1587
1628
|
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1629
|
+
context 'with a custom logger' do
|
1630
|
+
subject do
|
1631
|
+
Class.new(described_class) do
|
1632
|
+
def self.io
|
1633
|
+
@io ||= StringIO.new
|
1634
|
+
end
|
1635
|
+
logger ::Logger.new(io)
|
1636
|
+
end
|
1637
|
+
end
|
1594
1638
|
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
expect(subject.io).to receive(:write).with(message)
|
1601
|
-
subject.logger.info 'this will be logged'
|
1639
|
+
it 'exposes its interaface' do
|
1640
|
+
message = 'this will be logged'
|
1641
|
+
subject.logger.info message
|
1642
|
+
expect(subject.io.string).to include(message)
|
1643
|
+
end
|
1602
1644
|
end
|
1603
1645
|
|
1604
1646
|
it 'does not unnecessarily retain duplicate setup blocks' do
|
1605
1647
|
subject.logger
|
1606
|
-
expect { subject.logger }.
|
1648
|
+
expect { subject.logger }.not_to change(subject.instance_variable_get(:@setup), :size)
|
1607
1649
|
end
|
1608
1650
|
end
|
1609
1651
|
|
@@ -1729,13 +1771,13 @@ XML
|
|
1729
1771
|
end
|
1730
1772
|
|
1731
1773
|
get '/new/abc'
|
1732
|
-
expect(last_response.status).to
|
1774
|
+
expect(last_response.status).to be 404
|
1733
1775
|
get '/legacy/abc'
|
1734
|
-
expect(last_response.status).to
|
1776
|
+
expect(last_response.status).to be 200
|
1735
1777
|
get '/legacy/def'
|
1736
|
-
expect(last_response.status).to
|
1778
|
+
expect(last_response.status).to be 404
|
1737
1779
|
get '/new/def'
|
1738
|
-
expect(last_response.status).to
|
1780
|
+
expect(last_response.status).to be 200
|
1739
1781
|
end
|
1740
1782
|
end
|
1741
1783
|
|
@@ -1955,8 +1997,8 @@ XML
|
|
1955
1997
|
end
|
1956
1998
|
|
1957
1999
|
context 'with multiple apis' do
|
1958
|
-
let(:a) { Class.new(
|
1959
|
-
let(:b) { Class.new(
|
2000
|
+
let(:a) { Class.new(described_class) }
|
2001
|
+
let(:b) { Class.new(described_class) }
|
1960
2002
|
|
1961
2003
|
before do
|
1962
2004
|
a.helpers do
|
@@ -1990,7 +2032,7 @@ XML
|
|
1990
2032
|
raise 'rain!'
|
1991
2033
|
end
|
1992
2034
|
get '/exception'
|
1993
|
-
expect(last_response.status).to
|
2035
|
+
expect(last_response.status).to be 500
|
1994
2036
|
expect(last_response.body).to eq 'rain!'
|
1995
2037
|
end
|
1996
2038
|
|
@@ -2002,7 +2044,7 @@ XML
|
|
2002
2044
|
raise 'rain!'
|
2003
2045
|
end
|
2004
2046
|
get '/exception'
|
2005
|
-
expect(last_response.status).to
|
2047
|
+
expect(last_response.status).to be 500
|
2006
2048
|
expect(last_response.body).to eq({ error: 'rain!' }.to_json)
|
2007
2049
|
end
|
2008
2050
|
|
@@ -2012,7 +2054,7 @@ XML
|
|
2012
2054
|
subject.get('/unrescued') { raise 'beefcake' }
|
2013
2055
|
|
2014
2056
|
get '/rescued'
|
2015
|
-
expect(last_response.status).to
|
2057
|
+
expect(last_response.status).to be 500
|
2016
2058
|
|
2017
2059
|
expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
|
2018
2060
|
end
|
@@ -2031,10 +2073,10 @@ XML
|
|
2031
2073
|
subject.get('/standard_error') { raise StandardError }
|
2032
2074
|
|
2033
2075
|
get '/child_of_standard_error'
|
2034
|
-
expect(last_response.status).to
|
2076
|
+
expect(last_response.status).to be 402
|
2035
2077
|
|
2036
2078
|
get '/standard_error'
|
2037
|
-
expect(last_response.status).to
|
2079
|
+
expect(last_response.status).to be 401
|
2038
2080
|
end
|
2039
2081
|
|
2040
2082
|
context 'CustomError subclass of Grape::Exceptions::Base' do
|
@@ -2075,7 +2117,7 @@ XML
|
|
2075
2117
|
subject.get('/formatter_exception') { 'Hello world' }
|
2076
2118
|
|
2077
2119
|
get '/formatter_exception'
|
2078
|
-
expect(last_response.status).to
|
2120
|
+
expect(last_response.status).to be 500
|
2079
2121
|
expect(last_response.body).to eq('Formatter Error')
|
2080
2122
|
end
|
2081
2123
|
|
@@ -2085,7 +2127,7 @@ XML
|
|
2085
2127
|
|
2086
2128
|
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
|
2087
2129
|
get '/'
|
2088
|
-
expect(last_response.status).to
|
2130
|
+
expect(last_response.status).to be 500
|
2089
2131
|
expect(last_response.body).to eql 'Invalid response'
|
2090
2132
|
end
|
2091
2133
|
end
|
@@ -2099,14 +2141,16 @@ XML
|
|
2099
2141
|
raise 'rain!'
|
2100
2142
|
end
|
2101
2143
|
get '/exception'
|
2102
|
-
expect(last_response.status).to
|
2144
|
+
expect(last_response.status).to be 202
|
2103
2145
|
expect(last_response.body).to eq('rescued from rain!')
|
2104
2146
|
end
|
2105
2147
|
|
2106
2148
|
context 'custom errors' do
|
2107
2149
|
before do
|
2108
2150
|
class ConnectionError < RuntimeError; end
|
2151
|
+
|
2109
2152
|
class DatabaseError < RuntimeError; end
|
2153
|
+
|
2110
2154
|
class CommunicationError < StandardError; end
|
2111
2155
|
end
|
2112
2156
|
|
@@ -2118,9 +2162,10 @@ XML
|
|
2118
2162
|
raise ConnectionError
|
2119
2163
|
end
|
2120
2164
|
get '/exception'
|
2121
|
-
expect(last_response.status).to
|
2165
|
+
expect(last_response.status).to be 500
|
2122
2166
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2123
2167
|
end
|
2168
|
+
|
2124
2169
|
it 'rescues a specific error' do
|
2125
2170
|
subject.rescue_from ConnectionError do |e|
|
2126
2171
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2129,9 +2174,10 @@ XML
|
|
2129
2174
|
raise ConnectionError
|
2130
2175
|
end
|
2131
2176
|
get '/exception'
|
2132
|
-
expect(last_response.status).to
|
2177
|
+
expect(last_response.status).to be 500
|
2133
2178
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2134
2179
|
end
|
2180
|
+
|
2135
2181
|
it 'rescues a subclass of an error by default' do
|
2136
2182
|
subject.rescue_from RuntimeError do |e|
|
2137
2183
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2140,9 +2186,10 @@ XML
|
|
2140
2186
|
raise ConnectionError
|
2141
2187
|
end
|
2142
2188
|
get '/exception'
|
2143
|
-
expect(last_response.status).to
|
2189
|
+
expect(last_response.status).to be 500
|
2144
2190
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2145
2191
|
end
|
2192
|
+
|
2146
2193
|
it 'rescues multiple specific errors' do
|
2147
2194
|
subject.rescue_from ConnectionError do |e|
|
2148
2195
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2157,12 +2204,13 @@ XML
|
|
2157
2204
|
raise DatabaseError
|
2158
2205
|
end
|
2159
2206
|
get '/connection'
|
2160
|
-
expect(last_response.status).to
|
2207
|
+
expect(last_response.status).to be 500
|
2161
2208
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2162
2209
|
get '/database'
|
2163
|
-
expect(last_response.status).to
|
2210
|
+
expect(last_response.status).to be 500
|
2164
2211
|
expect(last_response.body).to eq('rescued from DatabaseError')
|
2165
2212
|
end
|
2213
|
+
|
2166
2214
|
it 'does not rescue a different error' do
|
2167
2215
|
subject.rescue_from RuntimeError do |e|
|
2168
2216
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2228,7 +2276,7 @@ XML
|
|
2228
2276
|
subject.rescue_from :all, with: :not_exist_method
|
2229
2277
|
subject.get('/rescue_method') { raise StandardError }
|
2230
2278
|
|
2231
|
-
expect { get '/rescue_method' }.to raise_error(NoMethodError,
|
2279
|
+
expect { get '/rescue_method' }.to raise_error(NoMethodError, /^undefined method 'not_exist_method'/)
|
2232
2280
|
end
|
2233
2281
|
|
2234
2282
|
it 'correctly chooses exception handler if :all handler is specified' do
|
@@ -2262,6 +2310,7 @@ XML
|
|
2262
2310
|
module ApiSpec
|
2263
2311
|
module APIErrors
|
2264
2312
|
class ParentError < StandardError; end
|
2313
|
+
|
2265
2314
|
class ChildError < ParentError; end
|
2266
2315
|
end
|
2267
2316
|
end
|
@@ -2282,9 +2331,9 @@ XML
|
|
2282
2331
|
end
|
2283
2332
|
|
2284
2333
|
get '/caught_child'
|
2285
|
-
expect(last_response.status).to
|
2334
|
+
expect(last_response.status).to be 500
|
2286
2335
|
get '/caught_parent'
|
2287
|
-
expect(last_response.status).to
|
2336
|
+
expect(last_response.status).to be 500
|
2288
2337
|
expect { get '/uncaught_parent' }.to raise_error(StandardError)
|
2289
2338
|
end
|
2290
2339
|
|
@@ -2297,7 +2346,7 @@ XML
|
|
2297
2346
|
end
|
2298
2347
|
|
2299
2348
|
get '/caught_child'
|
2300
|
-
expect(last_response.status).to
|
2349
|
+
expect(last_response.status).to be 500
|
2301
2350
|
end
|
2302
2351
|
|
2303
2352
|
it 'does not rescue child errors if rescue_subclasses is false' do
|
@@ -2392,7 +2441,7 @@ XML
|
|
2392
2441
|
end
|
2393
2442
|
|
2394
2443
|
context 'class' do
|
2395
|
-
before
|
2444
|
+
before do
|
2396
2445
|
module ApiSpec
|
2397
2446
|
class CustomErrorFormatter
|
2398
2447
|
def self.call(message, _backtrace, _options, _env, _original_exception)
|
@@ -2401,6 +2450,7 @@ XML
|
|
2401
2450
|
end
|
2402
2451
|
end
|
2403
2452
|
end
|
2453
|
+
|
2404
2454
|
it 'returns a custom error format' do
|
2405
2455
|
subject.rescue_from :all, backtrace: true
|
2406
2456
|
subject.error_formatter :txt, ApiSpec::CustomErrorFormatter
|
@@ -2414,7 +2464,7 @@ XML
|
|
2414
2464
|
|
2415
2465
|
describe 'with' do
|
2416
2466
|
context 'class' do
|
2417
|
-
before
|
2467
|
+
before do
|
2418
2468
|
module ApiSpec
|
2419
2469
|
class CustomErrorFormatter
|
2420
2470
|
def self.call(message, _backtrace, _option, _env, _original_exception)
|
@@ -2444,6 +2494,7 @@ XML
|
|
2444
2494
|
get '/exception'
|
2445
2495
|
expect(last_response.body).to eql '{"error":"rain!"}'
|
2446
2496
|
end
|
2497
|
+
|
2447
2498
|
it 'rescues all errors and return :json with backtrace' do
|
2448
2499
|
subject.rescue_from :all, backtrace: true
|
2449
2500
|
subject.format :json
|
@@ -2455,6 +2506,7 @@ XML
|
|
2455
2506
|
expect(json['error']).to eql 'rain!'
|
2456
2507
|
expect(json['backtrace'].length).to be > 0
|
2457
2508
|
end
|
2509
|
+
|
2458
2510
|
it 'rescues error! and return txt' do
|
2459
2511
|
subject.format :txt
|
2460
2512
|
subject.get '/error' do
|
@@ -2463,23 +2515,26 @@ XML
|
|
2463
2515
|
get '/error'
|
2464
2516
|
expect(last_response.body).to eql 'Access Denied'
|
2465
2517
|
end
|
2518
|
+
|
2466
2519
|
context 'with json format' do
|
2467
2520
|
before { subject.format :json }
|
2468
2521
|
|
2522
|
+
after do
|
2523
|
+
get '/error'
|
2524
|
+
expect(last_response.body).to eql('{"error":"failure"}')
|
2525
|
+
end
|
2526
|
+
|
2469
2527
|
it 'rescues error! called with a string and returns json' do
|
2470
2528
|
subject.get('/error') { error!(:failure, 401) }
|
2471
2529
|
end
|
2530
|
+
|
2472
2531
|
it 'rescues error! called with a symbol and returns json' do
|
2473
2532
|
subject.get('/error') { error!(:failure, 401) }
|
2474
2533
|
end
|
2534
|
+
|
2475
2535
|
it 'rescues error! called with a hash and returns json' do
|
2476
2536
|
subject.get('/error') { error!({ error: :failure }, 401) }
|
2477
2537
|
end
|
2478
|
-
|
2479
|
-
after do
|
2480
|
-
get '/error'
|
2481
|
-
expect(last_response.body).to eql('{"error":"failure"}')
|
2482
|
-
end
|
2483
2538
|
end
|
2484
2539
|
end
|
2485
2540
|
|
@@ -2492,6 +2547,7 @@ XML
|
|
2492
2547
|
get '/excel.xls'
|
2493
2548
|
expect(last_response.content_type).to eq('application/vnd.ms-excel')
|
2494
2549
|
end
|
2550
|
+
|
2495
2551
|
it 'allows to override content-type' do
|
2496
2552
|
subject.get :content do
|
2497
2553
|
content_type 'text/javascript'
|
@@ -2500,6 +2556,7 @@ XML
|
|
2500
2556
|
get '/content'
|
2501
2557
|
expect(last_response.content_type).to eq('text/javascript')
|
2502
2558
|
end
|
2559
|
+
|
2503
2560
|
it 'removes existing content types' do
|
2504
2561
|
subject.content_type :xls, 'application/vnd.ms-excel'
|
2505
2562
|
subject.get :excel do
|
@@ -2517,24 +2574,27 @@ XML
|
|
2517
2574
|
|
2518
2575
|
describe '.formatter' do
|
2519
2576
|
context 'multiple formatters' do
|
2520
|
-
before
|
2577
|
+
before do
|
2521
2578
|
subject.formatter :json, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
2522
2579
|
subject.formatter :txt, ->(object, _env) { "custom_formatter: #{object[:some]}" }
|
2523
2580
|
subject.get :simple do
|
2524
2581
|
{ some: 'hash' }
|
2525
2582
|
end
|
2526
2583
|
end
|
2584
|
+
|
2527
2585
|
it 'sets one formatter' do
|
2528
2586
|
get '/simple.json'
|
2529
2587
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2530
2588
|
end
|
2589
|
+
|
2531
2590
|
it 'sets another formatter' do
|
2532
2591
|
get '/simple.txt'
|
2533
2592
|
expect(last_response.body).to eql 'custom_formatter: hash'
|
2534
2593
|
end
|
2535
2594
|
end
|
2595
|
+
|
2536
2596
|
context 'custom formatter' do
|
2537
|
-
before
|
2597
|
+
before do
|
2538
2598
|
subject.content_type :json, 'application/json'
|
2539
2599
|
subject.content_type :custom, 'application/custom'
|
2540
2600
|
subject.formatter :custom, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
@@ -2542,15 +2602,18 @@ XML
|
|
2542
2602
|
{ some: 'hash' }
|
2543
2603
|
end
|
2544
2604
|
end
|
2605
|
+
|
2545
2606
|
it 'uses json' do
|
2546
2607
|
get '/simple.json'
|
2547
2608
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2548
2609
|
end
|
2610
|
+
|
2549
2611
|
it 'uses custom formatter' do
|
2550
2612
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2551
2613
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2552
2614
|
end
|
2553
2615
|
end
|
2616
|
+
|
2554
2617
|
context 'custom formatter class' do
|
2555
2618
|
module ApiSpec
|
2556
2619
|
module CustomFormatter
|
@@ -2559,7 +2622,7 @@ XML
|
|
2559
2622
|
end
|
2560
2623
|
end
|
2561
2624
|
end
|
2562
|
-
before
|
2625
|
+
before do
|
2563
2626
|
subject.content_type :json, 'application/json'
|
2564
2627
|
subject.content_type :custom, 'application/custom'
|
2565
2628
|
subject.formatter :custom, ApiSpec::CustomFormatter
|
@@ -2567,10 +2630,12 @@ XML
|
|
2567
2630
|
{ some: 'hash' }
|
2568
2631
|
end
|
2569
2632
|
end
|
2633
|
+
|
2570
2634
|
it 'uses json' do
|
2571
2635
|
get '/simple.json'
|
2572
2636
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2573
2637
|
end
|
2638
|
+
|
2574
2639
|
it 'uses custom formatter' do
|
2575
2640
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2576
2641
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
@@ -2588,8 +2653,9 @@ XML
|
|
2588
2653
|
expect(last_response.status).to eq(201)
|
2589
2654
|
expect(last_response.body).to eq('{"x":42}')
|
2590
2655
|
end
|
2656
|
+
|
2591
2657
|
context 'lambda parser' do
|
2592
|
-
before
|
2658
|
+
before do
|
2593
2659
|
subject.content_type :txt, 'text/plain'
|
2594
2660
|
subject.content_type :custom, 'text/custom'
|
2595
2661
|
subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }
|
@@ -2597,6 +2663,7 @@ XML
|
|
2597
2663
|
params[:simple]
|
2598
2664
|
end
|
2599
2665
|
end
|
2666
|
+
|
2600
2667
|
['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
|
2601
2668
|
it "uses parser for #{content_type}" do
|
2602
2669
|
put '/simple', 'simple', 'CONTENT_TYPE' => content_type
|
@@ -2605,6 +2672,7 @@ XML
|
|
2605
2672
|
end
|
2606
2673
|
end
|
2607
2674
|
end
|
2675
|
+
|
2608
2676
|
context 'custom parser class' do
|
2609
2677
|
module ApiSpec
|
2610
2678
|
module CustomParser
|
@@ -2613,7 +2681,7 @@ XML
|
|
2613
2681
|
end
|
2614
2682
|
end
|
2615
2683
|
end
|
2616
|
-
before
|
2684
|
+
before do
|
2617
2685
|
subject.content_type :txt, 'text/plain'
|
2618
2686
|
subject.content_type :custom, 'text/custom'
|
2619
2687
|
subject.parser :custom, ApiSpec::CustomParser
|
@@ -2621,12 +2689,14 @@ XML
|
|
2621
2689
|
params[:simple]
|
2622
2690
|
end
|
2623
2691
|
end
|
2692
|
+
|
2624
2693
|
it 'uses custom parser' do
|
2625
2694
|
put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
|
2626
2695
|
expect(last_response.status).to eq(200)
|
2627
2696
|
expect(last_response.body).to eql 'elpmis'
|
2628
2697
|
end
|
2629
2698
|
end
|
2699
|
+
|
2630
2700
|
if Object.const_defined? :MultiXml
|
2631
2701
|
context 'multi_xml' do
|
2632
2702
|
it "doesn't parse yaml" do
|
@@ -2651,12 +2721,13 @@ XML
|
|
2651
2721
|
end
|
2652
2722
|
end
|
2653
2723
|
context 'none parser class' do
|
2654
|
-
before
|
2724
|
+
before do
|
2655
2725
|
subject.parser :json, nil
|
2656
2726
|
subject.put 'data' do
|
2657
2727
|
"body: #{env['api.request.body']}"
|
2658
2728
|
end
|
2659
2729
|
end
|
2730
|
+
|
2660
2731
|
it 'does not parse data' do
|
2661
2732
|
put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
|
2662
2733
|
expect(last_response.status).to eq(200)
|
@@ -2666,10 +2737,11 @@ XML
|
|
2666
2737
|
end
|
2667
2738
|
|
2668
2739
|
describe '.default_format' do
|
2669
|
-
before
|
2740
|
+
before do
|
2670
2741
|
subject.format :json
|
2671
2742
|
subject.default_format :json
|
2672
2743
|
end
|
2744
|
+
|
2673
2745
|
it 'returns data in default format' do
|
2674
2746
|
subject.get '/data' do
|
2675
2747
|
{ x: 42 }
|
@@ -2678,6 +2750,7 @@ XML
|
|
2678
2750
|
expect(last_response.status).to eq(200)
|
2679
2751
|
expect(last_response.body).to eq('{"x":42}')
|
2680
2752
|
end
|
2753
|
+
|
2681
2754
|
it 'parses data in default format' do
|
2682
2755
|
subject.post '/data' do
|
2683
2756
|
{ x: params[:x] }
|
@@ -2696,16 +2769,18 @@ XML
|
|
2696
2769
|
raise 'rain!'
|
2697
2770
|
end
|
2698
2771
|
get '/exception'
|
2699
|
-
expect(last_response.status).to
|
2772
|
+
expect(last_response.status).to be 200
|
2700
2773
|
end
|
2774
|
+
|
2701
2775
|
it 'has a default error status' do
|
2702
2776
|
subject.rescue_from :all
|
2703
2777
|
subject.get '/exception' do
|
2704
2778
|
raise 'rain!'
|
2705
2779
|
end
|
2706
2780
|
get '/exception'
|
2707
|
-
expect(last_response.status).to
|
2781
|
+
expect(last_response.status).to be 500
|
2708
2782
|
end
|
2783
|
+
|
2709
2784
|
it 'uses the default error status in error!' do
|
2710
2785
|
subject.rescue_from :all
|
2711
2786
|
subject.default_error_status 400
|
@@ -2713,7 +2788,7 @@ XML
|
|
2713
2788
|
error! 'rain!'
|
2714
2789
|
end
|
2715
2790
|
get '/exception'
|
2716
|
-
expect(last_response.status).to
|
2791
|
+
expect(last_response.status).to be 400
|
2717
2792
|
end
|
2718
2793
|
end
|
2719
2794
|
|
@@ -2739,7 +2814,7 @@ XML
|
|
2739
2814
|
end
|
2740
2815
|
|
2741
2816
|
get '/exception'
|
2742
|
-
expect(last_response.status).to
|
2817
|
+
expect(last_response.status).to be 408
|
2743
2818
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2744
2819
|
end
|
2745
2820
|
|
@@ -2750,7 +2825,7 @@ XML
|
|
2750
2825
|
end
|
2751
2826
|
|
2752
2827
|
get '/exception'
|
2753
|
-
expect(last_response.status).to
|
2828
|
+
expect(last_response.status).to be 408
|
2754
2829
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2755
2830
|
end
|
2756
2831
|
end
|
@@ -2761,12 +2836,14 @@ XML
|
|
2761
2836
|
expect(subject.routes).to eq([])
|
2762
2837
|
end
|
2763
2838
|
end
|
2839
|
+
|
2764
2840
|
describe 'single method api structure' do
|
2765
|
-
before
|
2841
|
+
before do
|
2766
2842
|
subject.get :ping do
|
2767
2843
|
'pong'
|
2768
2844
|
end
|
2769
2845
|
end
|
2846
|
+
|
2770
2847
|
it 'returns one route' do
|
2771
2848
|
expect(subject.routes.size).to eq(1)
|
2772
2849
|
route = subject.routes[0]
|
@@ -2775,8 +2852,9 @@ XML
|
|
2775
2852
|
expect(route.request_method).to eq('GET')
|
2776
2853
|
end
|
2777
2854
|
end
|
2855
|
+
|
2778
2856
|
describe 'api structure with two versions and a namespace' do
|
2779
|
-
before
|
2857
|
+
before do
|
2780
2858
|
subject.version 'v1', using: :path
|
2781
2859
|
subject.get 'version' do
|
2782
2860
|
api.version
|
@@ -2792,30 +2870,37 @@ XML
|
|
2792
2870
|
end
|
2793
2871
|
end
|
2794
2872
|
end
|
2873
|
+
|
2795
2874
|
it 'returns the latest version set' do
|
2796
2875
|
expect(subject.version).to eq('v2')
|
2797
2876
|
end
|
2877
|
+
|
2798
2878
|
it 'returns versions' do
|
2799
2879
|
expect(subject.versions).to eq(%w[v1 v2])
|
2800
2880
|
end
|
2881
|
+
|
2801
2882
|
it 'sets route paths' do
|
2802
2883
|
expect(subject.routes.size).to be >= 2
|
2803
2884
|
expect(subject.routes[0].path).to eq('/:version/version(.:format)')
|
2804
2885
|
expect(subject.routes[1].path).to eq('/p/:version/n1/n2/version(.:format)')
|
2805
2886
|
end
|
2887
|
+
|
2806
2888
|
it 'sets route versions' do
|
2807
2889
|
expect(subject.routes[0].version).to eq('v1')
|
2808
2890
|
expect(subject.routes[1].version).to eq('v2')
|
2809
2891
|
end
|
2892
|
+
|
2810
2893
|
it 'sets a nested namespace' do
|
2811
2894
|
expect(subject.routes[1].namespace).to eq('/n1/n2')
|
2812
2895
|
end
|
2896
|
+
|
2813
2897
|
it 'sets prefix' do
|
2814
2898
|
expect(subject.routes[1].prefix).to eq('p')
|
2815
2899
|
end
|
2816
2900
|
end
|
2901
|
+
|
2817
2902
|
describe 'api structure with additional parameters' do
|
2818
|
-
before
|
2903
|
+
before do
|
2819
2904
|
subject.params do
|
2820
2905
|
requires :token, desc: 'a token'
|
2821
2906
|
optional :limit, desc: 'the limit'
|
@@ -2824,14 +2909,17 @@ XML
|
|
2824
2909
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
2825
2910
|
end
|
2826
2911
|
end
|
2912
|
+
|
2827
2913
|
it 'splits a string' do
|
2828
2914
|
get '/split/a,b,c.json', token: ','
|
2829
2915
|
expect(last_response.body).to eq('["a","b","c"]')
|
2830
2916
|
end
|
2917
|
+
|
2831
2918
|
it 'splits a string with limit' do
|
2832
2919
|
get '/split/a,b,c.json', token: ',', limit: '2'
|
2833
2920
|
expect(last_response.body).to eq('["a","b,c"]')
|
2834
2921
|
end
|
2922
|
+
|
2835
2923
|
it 'sets params' do
|
2836
2924
|
expect(subject.routes.map do |route|
|
2837
2925
|
{ params: route.params }
|
@@ -2846,8 +2934,9 @@ XML
|
|
2846
2934
|
]
|
2847
2935
|
end
|
2848
2936
|
end
|
2937
|
+
|
2849
2938
|
describe 'api structure with multiple apis' do
|
2850
|
-
before
|
2939
|
+
before do
|
2851
2940
|
subject.params do
|
2852
2941
|
requires :one, desc: 'a token'
|
2853
2942
|
optional :two, desc: 'the limit'
|
@@ -2862,6 +2951,7 @@ XML
|
|
2862
2951
|
subject.get 'two' do
|
2863
2952
|
end
|
2864
2953
|
end
|
2954
|
+
|
2865
2955
|
it 'sets params' do
|
2866
2956
|
expect(subject.routes.map do |route|
|
2867
2957
|
{ params: route.params }
|
@@ -2881,8 +2971,9 @@ XML
|
|
2881
2971
|
]
|
2882
2972
|
end
|
2883
2973
|
end
|
2974
|
+
|
2884
2975
|
describe 'api structure with an api without params' do
|
2885
|
-
before
|
2976
|
+
before do
|
2886
2977
|
subject.params do
|
2887
2978
|
requires :one, desc: 'a token'
|
2888
2979
|
optional :two, desc: 'the limit'
|
@@ -2893,6 +2984,7 @@ XML
|
|
2893
2984
|
subject.get 'two' do
|
2894
2985
|
end
|
2895
2986
|
end
|
2987
|
+
|
2896
2988
|
it 'sets params' do
|
2897
2989
|
expect(subject.routes.map do |route|
|
2898
2990
|
{ params: route.params }
|
@@ -2909,17 +3001,20 @@ XML
|
|
2909
3001
|
]
|
2910
3002
|
end
|
2911
3003
|
end
|
3004
|
+
|
2912
3005
|
describe 'api with a custom route setting' do
|
2913
|
-
before
|
3006
|
+
before do
|
2914
3007
|
subject.route_setting :custom, key: 'value'
|
2915
3008
|
subject.get 'one'
|
2916
3009
|
end
|
3010
|
+
|
2917
3011
|
it 'exposed' do
|
2918
3012
|
expect(subject.routes.count).to eq 1
|
2919
3013
|
route = subject.routes.first
|
2920
3014
|
expect(route.settings[:custom]).to eq(key: 'value')
|
2921
3015
|
end
|
2922
3016
|
end
|
3017
|
+
|
2923
3018
|
describe 'status' do
|
2924
3019
|
it 'can be set to arbitrary Integer value' do
|
2925
3020
|
subject.get '/foo' do
|
@@ -2928,6 +3023,7 @@ XML
|
|
2928
3023
|
get '/foo'
|
2929
3024
|
expect(last_response.status).to eq 210
|
2930
3025
|
end
|
3026
|
+
|
2931
3027
|
it 'can be set with a status code symbol' do
|
2932
3028
|
subject.get '/foo' do
|
2933
3029
|
status :see_other
|
@@ -2942,10 +3038,12 @@ XML
|
|
2942
3038
|
it 'empty array of routes' do
|
2943
3039
|
expect(subject.routes).to eq([])
|
2944
3040
|
end
|
3041
|
+
|
2945
3042
|
it 'empty array of routes' do
|
2946
3043
|
subject.desc 'grape api'
|
2947
3044
|
expect(subject.routes).to eq([])
|
2948
3045
|
end
|
3046
|
+
|
2949
3047
|
it 'describes a method' do
|
2950
3048
|
subject.desc 'first method'
|
2951
3049
|
subject.get :first
|
@@ -2954,8 +3052,9 @@ XML
|
|
2954
3052
|
expect(route.description).to eq('first method')
|
2955
3053
|
expect(route.route_foo).to be_nil
|
2956
3054
|
expect(route.params).to eq({})
|
2957
|
-
expect(route.options).to
|
3055
|
+
expect(route.options).to be_a(Hash)
|
2958
3056
|
end
|
3057
|
+
|
2959
3058
|
it 'has params which does not include format and version as named captures' do
|
2960
3059
|
subject.version :v1, using: :path
|
2961
3060
|
subject.get :first
|
@@ -2963,6 +3062,7 @@ XML
|
|
2963
3062
|
expect(param_keys).not_to include('format')
|
2964
3063
|
expect(param_keys).not_to include('version')
|
2965
3064
|
end
|
3065
|
+
|
2966
3066
|
it 'describes methods separately' do
|
2967
3067
|
subject.desc 'first method'
|
2968
3068
|
subject.get :first
|
@@ -2976,6 +3076,7 @@ XML
|
|
2976
3076
|
{ description: 'second method', params: {} }
|
2977
3077
|
]
|
2978
3078
|
end
|
3079
|
+
|
2979
3080
|
it 'resets desc' do
|
2980
3081
|
subject.desc 'first method'
|
2981
3082
|
subject.get :first
|
@@ -2987,6 +3088,7 @@ XML
|
|
2987
3088
|
{ description: nil, params: {} }
|
2988
3089
|
]
|
2989
3090
|
end
|
3091
|
+
|
2990
3092
|
it 'namespaces and describe arbitrary parameters' do
|
2991
3093
|
subject.namespace 'ns' do
|
2992
3094
|
desc 'ns second', foo: 'bar'
|
@@ -2998,6 +3100,7 @@ XML
|
|
2998
3100
|
{ description: 'ns second', foo: 'bar', params: {} }
|
2999
3101
|
]
|
3000
3102
|
end
|
3103
|
+
|
3001
3104
|
it 'includes details' do
|
3002
3105
|
subject.desc 'method', details: 'method details'
|
3003
3106
|
subject.get 'method'
|
@@ -3007,6 +3110,7 @@ XML
|
|
3007
3110
|
{ description: 'method', details: 'method details', params: {} }
|
3008
3111
|
]
|
3009
3112
|
end
|
3113
|
+
|
3010
3114
|
it 'describes a method with parameters' do
|
3011
3115
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
3012
3116
|
subject.get 'reverse' do
|
@@ -3018,6 +3122,7 @@ XML
|
|
3018
3122
|
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
|
3019
3123
|
]
|
3020
3124
|
end
|
3125
|
+
|
3021
3126
|
it 'does not inherit param descriptions in consequent namespaces' do
|
3022
3127
|
subject.desc 'global description'
|
3023
3128
|
subject.params do
|
@@ -3048,6 +3153,7 @@ XML
|
|
3048
3153
|
} }
|
3049
3154
|
]
|
3050
3155
|
end
|
3156
|
+
|
3051
3157
|
it 'merges the parameters of the namespace with the parameters of the method' do
|
3052
3158
|
subject.desc 'namespace'
|
3053
3159
|
subject.params do
|
@@ -3072,6 +3178,7 @@ XML
|
|
3072
3178
|
} }
|
3073
3179
|
]
|
3074
3180
|
end
|
3181
|
+
|
3075
3182
|
it 'merges the parameters of nested namespaces' do
|
3076
3183
|
subject.desc 'ns1'
|
3077
3184
|
subject.params do
|
@@ -3104,6 +3211,7 @@ XML
|
|
3104
3211
|
} }
|
3105
3212
|
]
|
3106
3213
|
end
|
3214
|
+
|
3107
3215
|
it 'groups nested params and prevents overwriting of params with same name in different groups' do
|
3108
3216
|
subject.desc 'method'
|
3109
3217
|
subject.params do
|
@@ -3119,14 +3227,15 @@ XML
|
|
3119
3227
|
subject.get 'method'
|
3120
3228
|
|
3121
3229
|
expect(subject.routes.map(&:params)).to eq [{
|
3122
|
-
'group1'
|
3230
|
+
'group1' => { required: true, type: 'Array' },
|
3123
3231
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3124
3232
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3125
|
-
'group2'
|
3233
|
+
'group2' => { required: true, type: 'Array' },
|
3126
3234
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3127
3235
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3128
3236
|
}]
|
3129
3237
|
end
|
3238
|
+
|
3130
3239
|
it 'uses full name of parameters in nested groups' do
|
3131
3240
|
subject.desc 'nesting'
|
3132
3241
|
subject.params do
|
@@ -3147,6 +3256,7 @@ XML
|
|
3147
3256
|
} }
|
3148
3257
|
]
|
3149
3258
|
end
|
3259
|
+
|
3150
3260
|
it 'allows to set the type attribute on :group element' do
|
3151
3261
|
subject.params do
|
3152
3262
|
group :foo, type: Array do
|
@@ -3154,6 +3264,7 @@ XML
|
|
3154
3264
|
end
|
3155
3265
|
end
|
3156
3266
|
end
|
3267
|
+
|
3157
3268
|
it 'parses parameters when no description is given' do
|
3158
3269
|
subject.params do
|
3159
3270
|
requires :one_param, desc: 'one param'
|
@@ -3165,6 +3276,7 @@ XML
|
|
3165
3276
|
{ description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
|
3166
3277
|
]
|
3167
3278
|
end
|
3279
|
+
|
3168
3280
|
it 'does not symbolize params' do
|
3169
3281
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
3170
3282
|
subject.get 'reverse/:s' do
|
@@ -3223,7 +3335,7 @@ XML
|
|
3223
3335
|
subject.version 'v1', using: :path
|
3224
3336
|
|
3225
3337
|
subject.namespace :cool do
|
3226
|
-
app = Class.new(Grape::API)
|
3338
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3227
3339
|
app.get('/awesome') do
|
3228
3340
|
'yo'
|
3229
3341
|
end
|
@@ -3239,12 +3351,12 @@ XML
|
|
3239
3351
|
subject.version 'v1', using: :path
|
3240
3352
|
|
3241
3353
|
subject.namespace :cool do
|
3242
|
-
inner_app = Class.new(Grape::API)
|
3354
|
+
inner_app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3243
3355
|
inner_app.get('/awesome') do
|
3244
3356
|
'yo'
|
3245
3357
|
end
|
3246
3358
|
|
3247
|
-
app = Class.new(Grape::API)
|
3359
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3248
3360
|
app.mount inner_app
|
3249
3361
|
mount app
|
3250
3362
|
end
|
@@ -3259,7 +3371,7 @@ XML
|
|
3259
3371
|
rack_response("rescued from #{e.message}", 202)
|
3260
3372
|
end
|
3261
3373
|
|
3262
|
-
app = Class.new(
|
3374
|
+
app = Class.new(described_class)
|
3263
3375
|
|
3264
3376
|
subject.namespace :mounted do
|
3265
3377
|
app.rescue_from ArgumentError
|
@@ -3268,15 +3380,16 @@ XML
|
|
3268
3380
|
end
|
3269
3381
|
|
3270
3382
|
get '/mounted/fail'
|
3271
|
-
expect(last_response.status).to
|
3383
|
+
expect(last_response.status).to be 202
|
3272
3384
|
expect(last_response.body).to eq('rescued from doh!')
|
3273
3385
|
end
|
3386
|
+
|
3274
3387
|
it 'prefers rescues defined by mounted if they rescue similar error class' do
|
3275
3388
|
subject.rescue_from StandardError do
|
3276
3389
|
rack_response('outer rescue')
|
3277
3390
|
end
|
3278
3391
|
|
3279
|
-
app = Class.new(
|
3392
|
+
app = Class.new(described_class)
|
3280
3393
|
|
3281
3394
|
subject.namespace :mounted do
|
3282
3395
|
rescue_from StandardError do
|
@@ -3289,12 +3402,13 @@ XML
|
|
3289
3402
|
get '/mounted/fail'
|
3290
3403
|
expect(last_response.body).to eq('inner rescue')
|
3291
3404
|
end
|
3405
|
+
|
3292
3406
|
it 'prefers rescues defined by mounted even if outer is more specific' do
|
3293
3407
|
subject.rescue_from ArgumentError do
|
3294
3408
|
rack_response('outer rescue')
|
3295
3409
|
end
|
3296
3410
|
|
3297
|
-
app = Class.new(
|
3411
|
+
app = Class.new(described_class)
|
3298
3412
|
|
3299
3413
|
subject.namespace :mounted do
|
3300
3414
|
rescue_from StandardError do
|
@@ -3307,12 +3421,13 @@ XML
|
|
3307
3421
|
get '/mounted/fail'
|
3308
3422
|
expect(last_response.body).to eq('inner rescue')
|
3309
3423
|
end
|
3424
|
+
|
3310
3425
|
it 'prefers more specific rescues defined by mounted' do
|
3311
3426
|
subject.rescue_from StandardError do
|
3312
3427
|
rack_response('outer rescue')
|
3313
3428
|
end
|
3314
3429
|
|
3315
|
-
app = Class.new(
|
3430
|
+
app = Class.new(described_class)
|
3316
3431
|
|
3317
3432
|
subject.namespace :mounted do
|
3318
3433
|
rescue_from ArgumentError do
|
@@ -3329,19 +3444,19 @@ XML
|
|
3329
3444
|
|
3330
3445
|
it 'collects the routes of the mounted api' do
|
3331
3446
|
subject.namespace :cool do
|
3332
|
-
app = Class.new(Grape::API)
|
3447
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3333
3448
|
app.get('/awesome') {}
|
3334
3449
|
app.post('/sauce') {}
|
3335
3450
|
mount app
|
3336
3451
|
end
|
3337
3452
|
expect(subject.routes.size).to eq(2)
|
3338
|
-
expect(subject.routes.first.path).to match(%r{
|
3339
|
-
expect(subject.routes.last.path).to match(%r{
|
3453
|
+
expect(subject.routes.first.path).to match(%r{/cool/awesome})
|
3454
|
+
expect(subject.routes.last.path).to match(%r{/cool/sauce})
|
3340
3455
|
end
|
3341
3456
|
|
3342
3457
|
it 'mounts on a path' do
|
3343
3458
|
subject.namespace :cool do
|
3344
|
-
app = Class.new(Grape::API)
|
3459
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3345
3460
|
app.get '/awesome' do
|
3346
3461
|
'sauce'
|
3347
3462
|
end
|
@@ -3353,12 +3468,12 @@ XML
|
|
3353
3468
|
end
|
3354
3469
|
|
3355
3470
|
it 'mounts on a nested path' do
|
3356
|
-
APP1 = Class.new(
|
3357
|
-
APP2 = Class.new(
|
3471
|
+
APP1 = Class.new(described_class)
|
3472
|
+
APP2 = Class.new(described_class)
|
3358
3473
|
APP2.get '/nice' do
|
3359
3474
|
'play'
|
3360
3475
|
end
|
3361
|
-
#
|
3476
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3362
3477
|
APP3 = subject
|
3363
3478
|
APP3.mount APP1 => '/app1'
|
3364
3479
|
APP1.mount APP2 => '/app2'
|
@@ -3370,7 +3485,7 @@ XML
|
|
3370
3485
|
end
|
3371
3486
|
|
3372
3487
|
it 'responds to options' do
|
3373
|
-
app = Class.new(
|
3488
|
+
app = Class.new(described_class)
|
3374
3489
|
app.get '/colour' do
|
3375
3490
|
'red'
|
3376
3491
|
end
|
@@ -3384,21 +3499,21 @@ XML
|
|
3384
3499
|
end
|
3385
3500
|
|
3386
3501
|
get '/apples/colour'
|
3387
|
-
expect(last_response.status).to
|
3502
|
+
expect(last_response.status).to be 200
|
3388
3503
|
expect(last_response.body).to eq('red')
|
3389
3504
|
options '/apples/colour'
|
3390
|
-
expect(last_response.status).to
|
3505
|
+
expect(last_response.status).to be 204
|
3391
3506
|
get '/apples/pears/colour'
|
3392
|
-
expect(last_response.status).to
|
3507
|
+
expect(last_response.status).to be 200
|
3393
3508
|
expect(last_response.body).to eq('green')
|
3394
3509
|
options '/apples/pears/colour'
|
3395
|
-
expect(last_response.status).to
|
3510
|
+
expect(last_response.status).to be 204
|
3396
3511
|
end
|
3397
3512
|
|
3398
3513
|
it 'responds to options with path versioning' do
|
3399
3514
|
subject.version 'v1', using: :path
|
3400
3515
|
subject.namespace :apples do
|
3401
|
-
app = Class.new(Grape::API)
|
3516
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3402
3517
|
app.get('/colour') do
|
3403
3518
|
'red'
|
3404
3519
|
end
|
@@ -3406,14 +3521,14 @@ XML
|
|
3406
3521
|
end
|
3407
3522
|
|
3408
3523
|
get '/v1/apples/colour'
|
3409
|
-
expect(last_response.status).to
|
3524
|
+
expect(last_response.status).to be 200
|
3410
3525
|
expect(last_response.body).to eq('red')
|
3411
3526
|
options '/v1/apples/colour'
|
3412
|
-
expect(last_response.status).to
|
3527
|
+
expect(last_response.status).to be 204
|
3413
3528
|
end
|
3414
3529
|
|
3415
3530
|
it 'mounts a versioned API with nested resources' do
|
3416
|
-
api = Class.new(
|
3531
|
+
api = Class.new(described_class) do
|
3417
3532
|
version 'v1'
|
3418
3533
|
resources :users do
|
3419
3534
|
get :hello do
|
@@ -3428,7 +3543,7 @@ XML
|
|
3428
3543
|
end
|
3429
3544
|
|
3430
3545
|
it 'mounts a prefixed API with nested resources' do
|
3431
|
-
api = Class.new(
|
3546
|
+
api = Class.new(described_class) do
|
3432
3547
|
prefix 'api'
|
3433
3548
|
resources :users do
|
3434
3549
|
get :hello do
|
@@ -3443,7 +3558,7 @@ XML
|
|
3443
3558
|
end
|
3444
3559
|
|
3445
3560
|
it 'applies format to a mounted API with nested resources' do
|
3446
|
-
api = Class.new(
|
3561
|
+
api = Class.new(described_class) do
|
3447
3562
|
format :json
|
3448
3563
|
resources :users do
|
3449
3564
|
get do
|
@@ -3458,7 +3573,7 @@ XML
|
|
3458
3573
|
end
|
3459
3574
|
|
3460
3575
|
it 'applies auth to a mounted API with nested resources' do
|
3461
|
-
api = Class.new(
|
3576
|
+
api = Class.new(described_class) do
|
3462
3577
|
format :json
|
3463
3578
|
http_basic do |username, password|
|
3464
3579
|
username == 'username' && password == 'password'
|
@@ -3479,7 +3594,7 @@ XML
|
|
3479
3594
|
end
|
3480
3595
|
|
3481
3596
|
it 'mounts multiple versioned APIs with nested resources' do
|
3482
|
-
api1 = Class.new(
|
3597
|
+
api1 = Class.new(described_class) do
|
3483
3598
|
version 'one', using: :header, vendor: 'test'
|
3484
3599
|
resources :users do
|
3485
3600
|
get :hello do
|
@@ -3488,7 +3603,7 @@ XML
|
|
3488
3603
|
end
|
3489
3604
|
end
|
3490
3605
|
|
3491
|
-
api2 = Class.new(
|
3606
|
+
api2 = Class.new(described_class) do
|
3492
3607
|
version 'two', using: :header, vendor: 'test'
|
3493
3608
|
resources :users do
|
3494
3609
|
get :hello do
|
@@ -3507,7 +3622,7 @@ XML
|
|
3507
3622
|
end
|
3508
3623
|
|
3509
3624
|
it 'recognizes potential versions with mounted path' do
|
3510
|
-
a = Class.new(
|
3625
|
+
a = Class.new(described_class) do
|
3511
3626
|
version :v1, using: :path
|
3512
3627
|
|
3513
3628
|
get '/hello' do
|
@@ -3515,7 +3630,7 @@ XML
|
|
3515
3630
|
end
|
3516
3631
|
end
|
3517
3632
|
|
3518
|
-
b = Class.new(
|
3633
|
+
b = Class.new(described_class) do
|
3519
3634
|
version :v1, using: :path
|
3520
3635
|
|
3521
3636
|
get '/world' do
|
@@ -3535,11 +3650,11 @@ XML
|
|
3535
3650
|
|
3536
3651
|
context 'when mounting class extends a subclass of Grape::API' do
|
3537
3652
|
it 'mounts APIs with the same superclass' do
|
3538
|
-
base_api = Class.new(
|
3653
|
+
base_api = Class.new(described_class)
|
3539
3654
|
a = Class.new(base_api)
|
3540
3655
|
b = Class.new(base_api)
|
3541
3656
|
|
3542
|
-
expect { a.mount b }.
|
3657
|
+
expect { a.mount b }.not_to raise_error
|
3543
3658
|
end
|
3544
3659
|
end
|
3545
3660
|
|
@@ -3549,6 +3664,7 @@ XML
|
|
3549
3664
|
def self.included(base)
|
3550
3665
|
base.extend(ClassMethods)
|
3551
3666
|
end
|
3667
|
+
|
3552
3668
|
module ClassMethods
|
3553
3669
|
def my_method
|
3554
3670
|
@test = true
|
@@ -3557,22 +3673,22 @@ XML
|
|
3557
3673
|
end
|
3558
3674
|
end
|
3559
3675
|
|
3560
|
-
it '
|
3676
|
+
it 'correctlies include module in nested mount' do
|
3561
3677
|
module_to_include = included_module
|
3562
|
-
v1 = Class.new(
|
3678
|
+
v1 = Class.new(described_class) do
|
3563
3679
|
version :v1, using: :path
|
3564
3680
|
include module_to_include
|
3565
3681
|
my_method
|
3566
3682
|
end
|
3567
|
-
v2 = Class.new(
|
3683
|
+
v2 = Class.new(described_class) do
|
3568
3684
|
version :v2, using: :path
|
3569
3685
|
end
|
3570
|
-
segment_base = Class.new(
|
3686
|
+
segment_base = Class.new(described_class) do
|
3571
3687
|
mount v1
|
3572
3688
|
mount v2
|
3573
3689
|
end
|
3574
3690
|
|
3575
|
-
Class.new(
|
3691
|
+
Class.new(described_class) do
|
3576
3692
|
mount segment_base
|
3577
3693
|
end
|
3578
3694
|
|
@@ -3594,7 +3710,7 @@ XML
|
|
3594
3710
|
it 'sets the instance' do
|
3595
3711
|
expect(subject.instance).to be_nil
|
3596
3712
|
subject.compile
|
3597
|
-
expect(subject.instance).to
|
3713
|
+
expect(subject.instance).to be_a(subject.base_instance)
|
3598
3714
|
end
|
3599
3715
|
end
|
3600
3716
|
|
@@ -3607,7 +3723,7 @@ XML
|
|
3607
3723
|
end
|
3608
3724
|
|
3609
3725
|
describe '.endpoint' do
|
3610
|
-
before
|
3726
|
+
before do
|
3611
3727
|
subject.format :json
|
3612
3728
|
subject.get '/endpoint/options' do
|
3613
3729
|
{
|
@@ -3616,6 +3732,7 @@ XML
|
|
3616
3732
|
}
|
3617
3733
|
end
|
3618
3734
|
end
|
3735
|
+
|
3619
3736
|
it 'path' do
|
3620
3737
|
get '/endpoint/options'
|
3621
3738
|
options = ::Grape::Json.load(last_response.body)
|
@@ -3627,7 +3744,7 @@ XML
|
|
3627
3744
|
|
3628
3745
|
describe '.route' do
|
3629
3746
|
context 'plain' do
|
3630
|
-
before
|
3747
|
+
before do
|
3631
3748
|
subject.get '/' do
|
3632
3749
|
route.path
|
3633
3750
|
end
|
@@ -3635,6 +3752,7 @@ XML
|
|
3635
3752
|
route.path
|
3636
3753
|
end
|
3637
3754
|
end
|
3755
|
+
|
3638
3756
|
it 'provides access to route info' do
|
3639
3757
|
get '/'
|
3640
3758
|
expect(last_response.body).to eq('/(.:format)')
|
@@ -3642,8 +3760,9 @@ XML
|
|
3642
3760
|
expect(last_response.body).to eq('/path(.:format)')
|
3643
3761
|
end
|
3644
3762
|
end
|
3763
|
+
|
3645
3764
|
context 'with desc' do
|
3646
|
-
before
|
3765
|
+
before do
|
3647
3766
|
subject.desc 'returns description'
|
3648
3767
|
subject.get '/description' do
|
3649
3768
|
route.description
|
@@ -3653,82 +3772,98 @@ XML
|
|
3653
3772
|
route.params[params[:id]]
|
3654
3773
|
end
|
3655
3774
|
end
|
3775
|
+
|
3656
3776
|
it 'returns route description' do
|
3657
3777
|
get '/description'
|
3658
3778
|
expect(last_response.body).to eq('returns description')
|
3659
3779
|
end
|
3780
|
+
|
3660
3781
|
it 'returns route parameters' do
|
3661
3782
|
get '/params/x'
|
3662
3783
|
expect(last_response.body).to eq('y')
|
3663
3784
|
end
|
3664
3785
|
end
|
3665
3786
|
end
|
3787
|
+
|
3666
3788
|
describe '.format' do
|
3667
3789
|
context ':txt' do
|
3668
|
-
before
|
3790
|
+
before do
|
3669
3791
|
subject.format :txt
|
3670
3792
|
subject.content_type :json, 'application/json'
|
3671
3793
|
subject.get '/meaning_of_life' do
|
3672
3794
|
{ meaning_of_life: 42 }
|
3673
3795
|
end
|
3674
3796
|
end
|
3797
|
+
|
3675
3798
|
it 'forces txt without an extension' do
|
3676
3799
|
get '/meaning_of_life'
|
3677
3800
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3678
3801
|
end
|
3802
|
+
|
3679
3803
|
it 'does not force txt with an extension' do
|
3680
3804
|
get '/meaning_of_life.json'
|
3681
3805
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3682
3806
|
end
|
3807
|
+
|
3683
3808
|
it 'forces txt from a non-accepting header' do
|
3684
3809
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3685
3810
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3686
3811
|
end
|
3687
3812
|
end
|
3813
|
+
|
3688
3814
|
context ':txt only' do
|
3689
|
-
before
|
3815
|
+
before do
|
3690
3816
|
subject.format :txt
|
3691
3817
|
subject.get '/meaning_of_life' do
|
3692
3818
|
{ meaning_of_life: 42 }
|
3693
3819
|
end
|
3694
3820
|
end
|
3821
|
+
|
3695
3822
|
it 'forces txt without an extension' do
|
3696
3823
|
get '/meaning_of_life'
|
3697
3824
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3698
3825
|
end
|
3826
|
+
|
3699
3827
|
it 'accepts specified extension' do
|
3700
3828
|
get '/meaning_of_life.txt'
|
3701
3829
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3702
3830
|
end
|
3831
|
+
|
3703
3832
|
it 'does not accept extensions other than specified' do
|
3704
3833
|
get '/meaning_of_life.json'
|
3705
3834
|
expect(last_response.status).to eq(404)
|
3706
3835
|
end
|
3836
|
+
|
3707
3837
|
it 'forces txt from a non-accepting header' do
|
3708
3838
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3709
3839
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3710
3840
|
end
|
3711
3841
|
end
|
3842
|
+
|
3712
3843
|
context ':json' do
|
3713
|
-
before
|
3844
|
+
before do
|
3714
3845
|
subject.format :json
|
3715
3846
|
subject.content_type :txt, 'text/plain'
|
3716
3847
|
subject.get '/meaning_of_life' do
|
3717
3848
|
{ meaning_of_life: 42 }
|
3718
3849
|
end
|
3719
3850
|
end
|
3851
|
+
|
3720
3852
|
it 'forces json without an extension' do
|
3721
3853
|
get '/meaning_of_life'
|
3722
3854
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3723
3855
|
end
|
3856
|
+
|
3724
3857
|
it 'does not force json with an extension' do
|
3725
3858
|
get '/meaning_of_life.txt'
|
3726
3859
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3727
3860
|
end
|
3861
|
+
|
3728
3862
|
it 'forces json from a non-accepting header' do
|
3729
3863
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
|
3730
3864
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3731
3865
|
end
|
3866
|
+
|
3732
3867
|
it 'can be overwritten with an explicit content type' do
|
3733
3868
|
subject.get '/meaning_of_life_with_content_type' do
|
3734
3869
|
content_type 'text/plain'
|
@@ -3737,6 +3872,7 @@ XML
|
|
3737
3872
|
get '/meaning_of_life_with_content_type'
|
3738
3873
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3739
3874
|
end
|
3875
|
+
|
3740
3876
|
it 'raised :error from middleware' do
|
3741
3877
|
middleware = Class.new(Grape::Middleware::Base) do
|
3742
3878
|
def before
|
@@ -3751,6 +3887,7 @@ XML
|
|
3751
3887
|
expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
|
3752
3888
|
end
|
3753
3889
|
end
|
3890
|
+
|
3754
3891
|
context ':serializable_hash' do
|
3755
3892
|
class SerializableHashExample
|
3756
3893
|
def serializable_hash
|
@@ -3758,9 +3895,10 @@ XML
|
|
3758
3895
|
end
|
3759
3896
|
end
|
3760
3897
|
|
3761
|
-
before
|
3898
|
+
before do
|
3762
3899
|
subject.format :serializable_hash
|
3763
3900
|
end
|
3901
|
+
|
3764
3902
|
it 'instance' do
|
3765
3903
|
subject.get '/example' do
|
3766
3904
|
SerializableHashExample.new
|
@@ -3768,6 +3906,7 @@ XML
|
|
3768
3906
|
get '/example'
|
3769
3907
|
expect(last_response.body).to eq('{"abc":"def"}')
|
3770
3908
|
end
|
3909
|
+
|
3771
3910
|
it 'root' do
|
3772
3911
|
subject.get '/example' do
|
3773
3912
|
{ 'root' => SerializableHashExample.new }
|
@@ -3775,6 +3914,7 @@ XML
|
|
3775
3914
|
get '/example'
|
3776
3915
|
expect(last_response.body).to eq('{"root":{"abc":"def"}}')
|
3777
3916
|
end
|
3917
|
+
|
3778
3918
|
it 'array' do
|
3779
3919
|
subject.get '/examples' do
|
3780
3920
|
[SerializableHashExample.new, SerializableHashExample.new]
|
@@ -3783,23 +3923,26 @@ XML
|
|
3783
3923
|
expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
|
3784
3924
|
end
|
3785
3925
|
end
|
3926
|
+
|
3786
3927
|
context ':xml' do
|
3787
|
-
before
|
3928
|
+
before do
|
3788
3929
|
subject.format :xml
|
3789
3930
|
end
|
3931
|
+
|
3790
3932
|
it 'string' do
|
3791
3933
|
subject.get '/example' do
|
3792
3934
|
'example'
|
3793
3935
|
end
|
3794
3936
|
get '/example'
|
3795
3937
|
expect(last_response.status).to eq(500)
|
3796
|
-
expect(last_response.body).to eq
|
3797
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3798
|
-
<error>
|
3799
|
-
|
3800
|
-
</error>
|
3801
|
-
XML
|
3938
|
+
expect(last_response.body).to eq <<~XML
|
3939
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3940
|
+
<error>
|
3941
|
+
<message>cannot convert String to xml</message>
|
3942
|
+
</error>
|
3943
|
+
XML
|
3802
3944
|
end
|
3945
|
+
|
3803
3946
|
it 'hash' do
|
3804
3947
|
subject.get '/example' do
|
3805
3948
|
{
|
@@ -3809,28 +3952,30 @@ XML
|
|
3809
3952
|
end
|
3810
3953
|
get '/example'
|
3811
3954
|
expect(last_response.status).to eq(200)
|
3812
|
-
expect(last_response.body).to eq
|
3813
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3814
|
-
<hash>
|
3815
|
-
|
3816
|
-
|
3817
|
-
</hash>
|
3818
|
-
XML
|
3955
|
+
expect(last_response.body).to eq <<~XML
|
3956
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3957
|
+
<hash>
|
3958
|
+
<example1>example1</example1>
|
3959
|
+
<example2>example2</example2>
|
3960
|
+
</hash>
|
3961
|
+
XML
|
3819
3962
|
end
|
3963
|
+
|
3820
3964
|
it 'array' do
|
3821
3965
|
subject.get '/example' do
|
3822
3966
|
%w[example1 example2]
|
3823
3967
|
end
|
3824
3968
|
get '/example'
|
3825
3969
|
expect(last_response.status).to eq(200)
|
3826
|
-
expect(last_response.body).to eq
|
3827
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3828
|
-
<strings type="array">
|
3829
|
-
|
3830
|
-
|
3831
|
-
</strings>
|
3832
|
-
XML
|
3970
|
+
expect(last_response.body).to eq <<~XML
|
3971
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3972
|
+
<strings type="array">
|
3973
|
+
<string>example1</string>
|
3974
|
+
<string>example2</string>
|
3975
|
+
</strings>
|
3976
|
+
XML
|
3833
3977
|
end
|
3978
|
+
|
3834
3979
|
it 'raised :error from middleware' do
|
3835
3980
|
middleware = Class.new(Grape::Middleware::Base) do
|
3836
3981
|
def before
|
@@ -3842,12 +3987,12 @@ XML
|
|
3842
3987
|
end
|
3843
3988
|
get '/'
|
3844
3989
|
expect(last_response.status).to eq(42)
|
3845
|
-
expect(last_response.body).to eq
|
3846
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3847
|
-
<error>
|
3848
|
-
|
3849
|
-
</error>
|
3850
|
-
XML
|
3990
|
+
expect(last_response.body).to eq <<~XML
|
3991
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3992
|
+
<error>
|
3993
|
+
<message>Unauthorized</message>
|
3994
|
+
</error>
|
3995
|
+
XML
|
3851
3996
|
end
|
3852
3997
|
end
|
3853
3998
|
end
|
@@ -3892,12 +4037,12 @@ XML
|
|
3892
4037
|
|
3893
4038
|
context 'catch-all' do
|
3894
4039
|
before do
|
3895
|
-
api1 = Class.new(
|
4040
|
+
api1 = Class.new(described_class)
|
3896
4041
|
api1.version 'v1', using: :path
|
3897
4042
|
api1.get 'hello' do
|
3898
4043
|
'v1'
|
3899
4044
|
end
|
3900
|
-
api2 = Class.new(
|
4045
|
+
api2 = Class.new(described_class)
|
3901
4046
|
api2.version 'v2', using: :path
|
3902
4047
|
api2.get 'hello' do
|
3903
4048
|
'v2'
|
@@ -3905,6 +4050,7 @@ XML
|
|
3905
4050
|
subject.mount api1
|
3906
4051
|
subject.mount api2
|
3907
4052
|
end
|
4053
|
+
|
3908
4054
|
[true, false].each do |anchor|
|
3909
4055
|
it "anchor=#{anchor}" do
|
3910
4056
|
subject.route :any, '*path', anchor: anchor do
|
@@ -3937,6 +4083,7 @@ XML
|
|
3937
4083
|
expect(last_response.status).to eq(404)
|
3938
4084
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3939
4085
|
end
|
4086
|
+
|
3940
4087
|
it 'does not cascade' do
|
3941
4088
|
subject.version 'v2', using: :path, cascade: false
|
3942
4089
|
get '/v2/hello'
|
@@ -3944,6 +4091,7 @@ XML
|
|
3944
4091
|
expect(last_response.headers.keys).not_to include 'X-Cascade'
|
3945
4092
|
end
|
3946
4093
|
end
|
4094
|
+
|
3947
4095
|
context 'via endpoint' do
|
3948
4096
|
it 'cascades' do
|
3949
4097
|
subject.cascade true
|
@@ -3951,6 +4099,7 @@ XML
|
|
3951
4099
|
expect(last_response.status).to eq(404)
|
3952
4100
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3953
4101
|
end
|
4102
|
+
|
3954
4103
|
it 'does not cascade' do
|
3955
4104
|
subject.cascade false
|
3956
4105
|
get '/hello'
|
@@ -3993,6 +4142,20 @@ XML
|
|
3993
4142
|
end
|
3994
4143
|
end
|
3995
4144
|
|
4145
|
+
context 'with non-UTF-8 characters in specified format' do
|
4146
|
+
it 'converts the characters' do
|
4147
|
+
subject.format :json
|
4148
|
+
subject.content_type :json, 'application/json'
|
4149
|
+
subject.get '/something' do
|
4150
|
+
'foo'
|
4151
|
+
end
|
4152
|
+
get '/something?format=%0A%0B%BF'
|
4153
|
+
expect(last_response.status).to eq(406)
|
4154
|
+
message = "The requested format '\n\u000b\357\277\275' is not supported."
|
4155
|
+
expect(last_response.body).to eq({ error: message }.to_json)
|
4156
|
+
end
|
4157
|
+
end
|
4158
|
+
|
3996
4159
|
context 'body' do
|
3997
4160
|
context 'false' do
|
3998
4161
|
before do
|
@@ -4000,12 +4163,14 @@ XML
|
|
4000
4163
|
body false
|
4001
4164
|
end
|
4002
4165
|
end
|
4166
|
+
|
4003
4167
|
it 'returns blank body' do
|
4004
4168
|
get '/blank'
|
4005
4169
|
expect(last_response.status).to eq(204)
|
4006
4170
|
expect(last_response.body).to be_blank
|
4007
4171
|
end
|
4008
4172
|
end
|
4173
|
+
|
4009
4174
|
context 'plain text' do
|
4010
4175
|
before do
|
4011
4176
|
subject.get '/text' do
|
@@ -4014,6 +4179,7 @@ XML
|
|
4014
4179
|
'ignored'
|
4015
4180
|
end
|
4016
4181
|
end
|
4182
|
+
|
4017
4183
|
it 'returns blank body' do
|
4018
4184
|
get '/text'
|
4019
4185
|
expect(last_response.status).to eq(200)
|
@@ -4023,7 +4189,7 @@ XML
|
|
4023
4189
|
end
|
4024
4190
|
|
4025
4191
|
describe 'normal class methods' do
|
4026
|
-
subject(:grape_api) { Class.new(
|
4192
|
+
subject(:grape_api) { Class.new(described_class) }
|
4027
4193
|
|
4028
4194
|
before do
|
4029
4195
|
stub_const('MyAPI', grape_api)
|
@@ -4040,10 +4206,54 @@ XML
|
|
4040
4206
|
end
|
4041
4207
|
end
|
4042
4208
|
|
4209
|
+
describe '.inherited' do
|
4210
|
+
context 'overriding within class' do
|
4211
|
+
let(:root_api) do
|
4212
|
+
Class.new(described_class) do
|
4213
|
+
@bar = 'Hello, world'
|
4214
|
+
|
4215
|
+
def self.inherited(child_api)
|
4216
|
+
super
|
4217
|
+
child_api.instance_variable_set(:@foo, @bar.dup)
|
4218
|
+
end
|
4219
|
+
end
|
4220
|
+
end
|
4221
|
+
|
4222
|
+
let(:child_api) { Class.new(root_api) }
|
4223
|
+
|
4224
|
+
it 'allows overriding the hook' do
|
4225
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4226
|
+
end
|
4227
|
+
end
|
4228
|
+
|
4229
|
+
context 'overriding via composition' do
|
4230
|
+
module Inherited
|
4231
|
+
def inherited(api)
|
4232
|
+
super
|
4233
|
+
api.instance_variable_set(:@foo, @bar.dup)
|
4234
|
+
end
|
4235
|
+
end
|
4236
|
+
|
4237
|
+
let(:root_api) do
|
4238
|
+
Class.new(described_class) do
|
4239
|
+
@bar = 'Hello, world'
|
4240
|
+
extend Inherited
|
4241
|
+
end
|
4242
|
+
end
|
4243
|
+
|
4244
|
+
let(:child_api) { Class.new(root_api) }
|
4245
|
+
|
4246
|
+
it 'allows overriding the hook' do
|
4247
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4248
|
+
end
|
4249
|
+
end
|
4250
|
+
end
|
4251
|
+
|
4043
4252
|
describe 'const_missing' do
|
4044
|
-
subject(:grape_api) { Class.new(
|
4253
|
+
subject(:grape_api) { Class.new(described_class) }
|
4254
|
+
|
4045
4255
|
let(:mounted) do
|
4046
|
-
Class.new(
|
4256
|
+
Class.new(described_class) do
|
4047
4257
|
get '/missing' do
|
4048
4258
|
SomeRandomConstant
|
4049
4259
|
end
|
@@ -4058,6 +4268,12 @@ XML
|
|
4058
4268
|
end
|
4059
4269
|
|
4060
4270
|
describe 'custom route helpers on nested APIs' do
|
4271
|
+
subject(:grape_api) do
|
4272
|
+
Class.new(described_class) do
|
4273
|
+
version 'v1', using: :path
|
4274
|
+
end
|
4275
|
+
end
|
4276
|
+
|
4061
4277
|
let(:shared_api_module) do
|
4062
4278
|
Module.new do
|
4063
4279
|
# rubocop:disable Style/ExplicitBlockArgument because this causes
|
@@ -4091,7 +4307,7 @@ XML
|
|
4091
4307
|
let(:orders_root) do
|
4092
4308
|
shared = shared_api_definitions
|
4093
4309
|
find = orders_find_endpoint
|
4094
|
-
Class.new(
|
4310
|
+
Class.new(described_class) do
|
4095
4311
|
include shared
|
4096
4312
|
|
4097
4313
|
namespace(:orders) do
|
@@ -4101,7 +4317,7 @@ XML
|
|
4101
4317
|
end
|
4102
4318
|
let(:orders_find_endpoint) do
|
4103
4319
|
shared = shared_api_definitions
|
4104
|
-
Class.new(
|
4320
|
+
Class.new(described_class) do
|
4105
4321
|
include shared
|
4106
4322
|
|
4107
4323
|
uniqe_id_route do
|
@@ -4112,11 +4328,6 @@ XML
|
|
4112
4328
|
end
|
4113
4329
|
end
|
4114
4330
|
end
|
4115
|
-
subject(:grape_api) do
|
4116
|
-
Class.new(Grape::API) do
|
4117
|
-
version 'v1', using: :path
|
4118
|
-
end
|
4119
|
-
end
|
4120
4331
|
|
4121
4332
|
before do
|
4122
4333
|
Grape::API::Instance.extend(shared_api_module)
|