grape 1.5.3 → 1.7.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/CHANGELOG.md +67 -0
- data/CONTRIBUTING.md +2 -1
- data/README.md +150 -21
- data/UPGRADING.md +61 -4
- data/grape.gemspec +5 -5
- 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 +2 -19
- 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 +8 -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 +20 -35
- data/lib/grape/error_formatter/json.rb +9 -7
- data/lib/grape/error_formatter/xml.rb +2 -6
- data/lib/grape/exceptions/base.rb +2 -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 +2 -2
- 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 +3 -0
- 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/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 +137 -78
- 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/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 +69 -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 +457 -231
- data/spec/grape/config_spec.rb +0 -2
- data/spec/grape/dsl/callbacks_spec.rb +2 -3
- data/spec/grape/dsl/configuration_spec.rb +0 -2
- data/spec/grape/dsl/desc_spec.rb +0 -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 +86 -58
- 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 +61 -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 +0 -2
- 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 +24 -17
- 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 +27 -8
- 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/attributes_iterator_spec.rb +0 -2
- 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 +182 -179
- data/spec/grape/validations_spec.rb +149 -80
- 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 +126 -117
- 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/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,
|
@@ -120,7 +122,7 @@ describe Grape::API do
|
|
120
122
|
end
|
121
123
|
|
122
124
|
describe '.version using accept_version_header' do
|
123
|
-
|
125
|
+
it_behaves_like 'versioning' do
|
124
126
|
let(:macro_options) do
|
125
127
|
{
|
126
128
|
using: :accept_version_header
|
@@ -389,7 +391,7 @@ describe Grape::API do
|
|
389
391
|
end
|
390
392
|
end
|
391
393
|
|
392
|
-
before
|
394
|
+
before do
|
393
395
|
allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_json).and_return('abc')
|
394
396
|
allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_txt).and_return('def')
|
395
397
|
|
@@ -447,7 +449,7 @@ describe Grape::API do
|
|
447
449
|
end
|
448
450
|
|
449
451
|
%i[put post].each do |verb|
|
450
|
-
context verb do
|
452
|
+
context verb.to_s do
|
451
453
|
['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
|
452
454
|
it "allows a(n) #{object.class} json object in params" do
|
453
455
|
subject.format :json
|
@@ -459,6 +461,7 @@ describe Grape::API do
|
|
459
461
|
expect(last_response.body).to eql ::Grape::Json.dump(object)
|
460
462
|
expect(last_request.params).to eql({})
|
461
463
|
end
|
464
|
+
|
462
465
|
it 'stores input in api.request.input' do
|
463
466
|
subject.format :json
|
464
467
|
subject.send(verb) do
|
@@ -468,6 +471,7 @@ describe Grape::API do
|
|
468
471
|
expect(last_response.status).to eq(verb == :post ? 201 : 200)
|
469
472
|
expect(last_response.body).to eql ::Grape::Json.dump(object).to_json
|
470
473
|
end
|
474
|
+
|
471
475
|
context 'chunked transfer encoding' do
|
472
476
|
it 'stores input in api.request.input' do
|
473
477
|
subject.format :json
|
@@ -562,7 +566,8 @@ describe Grape::API do
|
|
562
566
|
send(other_verb, '/example')
|
563
567
|
expected_rc = if other_verb == 'options' then 204
|
564
568
|
elsif other_verb == 'head' && verb == 'get' then 200
|
565
|
-
else
|
569
|
+
else
|
570
|
+
405
|
566
571
|
end
|
567
572
|
expect(last_response.status).to eql expected_rc
|
568
573
|
end
|
@@ -575,7 +580,7 @@ describe Grape::API do
|
|
575
580
|
end
|
576
581
|
|
577
582
|
post '/example'
|
578
|
-
expect(last_response.status).to
|
583
|
+
expect(last_response.status).to be 201
|
579
584
|
expect(last_response.body).to eql 'Created'
|
580
585
|
end
|
581
586
|
|
@@ -585,7 +590,7 @@ describe Grape::API do
|
|
585
590
|
'example'
|
586
591
|
end
|
587
592
|
put '/example'
|
588
|
-
expect(last_response.status).to
|
593
|
+
expect(last_response.status).to be 405
|
589
594
|
expect(last_response.body).to eql '405 Not Allowed'
|
590
595
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
591
596
|
end
|
@@ -593,15 +598,17 @@ describe Grape::API do
|
|
593
598
|
it 'runs only the before filter on 405 bad method' do
|
594
599
|
subject.namespace :example do
|
595
600
|
before { header 'X-Custom-Header', 'foo' }
|
601
|
+
|
596
602
|
before_validation { raise 'before_validation filter should not run' }
|
597
603
|
after_validation { raise 'after_validation filter should not run' }
|
598
604
|
after { raise 'after filter should not run' }
|
605
|
+
|
599
606
|
params { requires :only_for_get }
|
600
607
|
get
|
601
608
|
end
|
602
609
|
|
603
610
|
post '/example'
|
604
|
-
expect(last_response.status).to
|
611
|
+
expect(last_response.status).to be 405
|
605
612
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
606
613
|
end
|
607
614
|
|
@@ -610,29 +617,33 @@ describe Grape::API do
|
|
610
617
|
subject.namespace :example do
|
611
618
|
before do
|
612
619
|
raise 'before filter ran twice' if already_run
|
620
|
+
|
613
621
|
already_run = true
|
614
622
|
header 'X-Custom-Header', 'foo'
|
615
623
|
end
|
624
|
+
|
616
625
|
get
|
617
626
|
end
|
618
627
|
|
619
628
|
post '/example'
|
620
|
-
expect(last_response.status).to
|
629
|
+
expect(last_response.status).to be 405
|
621
630
|
expect(last_response.headers['X-Custom-Header']).to eql 'foo'
|
622
631
|
end
|
623
632
|
|
624
633
|
it 'runs all filters and body with a custom OPTIONS method' do
|
625
634
|
subject.namespace :example do
|
626
635
|
before { header 'X-Custom-Header-1', 'foo' }
|
636
|
+
|
627
637
|
before_validation { header 'X-Custom-Header-2', 'foo' }
|
628
638
|
after_validation { header 'X-Custom-Header-3', 'foo' }
|
629
639
|
after { header 'X-Custom-Header-4', 'foo' }
|
640
|
+
|
630
641
|
options { 'yup' }
|
631
642
|
get
|
632
643
|
end
|
633
644
|
|
634
645
|
options '/example'
|
635
|
-
expect(last_response.status).to
|
646
|
+
expect(last_response.status).to be 200
|
636
647
|
expect(last_response.body).to eql 'yup'
|
637
648
|
expect(last_response.headers['Allow']).to be_nil
|
638
649
|
expect(last_response.headers['X-Custom-Header-1']).to eql 'foo'
|
@@ -649,13 +660,13 @@ describe Grape::API do
|
|
649
660
|
end
|
650
661
|
|
651
662
|
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
|
663
|
+
expect(last_response.status).to be 405
|
664
|
+
expect(last_response.body).to eq <<~XML
|
665
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
666
|
+
<error>
|
667
|
+
<message>405 Not Allowed</message>
|
668
|
+
</error>
|
669
|
+
XML
|
659
670
|
end
|
660
671
|
end
|
661
672
|
|
@@ -669,7 +680,7 @@ XML
|
|
669
680
|
'example'
|
670
681
|
end
|
671
682
|
put '/example'
|
672
|
-
expect(last_response.status).to
|
683
|
+
expect(last_response.status).to be 405
|
673
684
|
expect(last_response.body).to eql '405 Not Allowed'
|
674
685
|
end
|
675
686
|
end
|
@@ -713,7 +724,7 @@ XML
|
|
713
724
|
end
|
714
725
|
|
715
726
|
it 'returns a 204' do
|
716
|
-
expect(last_response.status).to
|
727
|
+
expect(last_response.status).to be 204
|
717
728
|
end
|
718
729
|
|
719
730
|
it 'has an empty body' do
|
@@ -749,11 +760,53 @@ XML
|
|
749
760
|
end
|
750
761
|
end
|
751
762
|
|
763
|
+
describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
|
764
|
+
before do
|
765
|
+
subject.namespace :example do
|
766
|
+
get do
|
767
|
+
'example'
|
768
|
+
end
|
769
|
+
|
770
|
+
patch do
|
771
|
+
'example'
|
772
|
+
end
|
773
|
+
|
774
|
+
post do
|
775
|
+
'example'
|
776
|
+
end
|
777
|
+
|
778
|
+
delete do
|
779
|
+
'example'
|
780
|
+
end
|
781
|
+
|
782
|
+
put do
|
783
|
+
'example'
|
784
|
+
end
|
785
|
+
end
|
786
|
+
options '/example'
|
787
|
+
end
|
788
|
+
|
789
|
+
describe 'it adds an OPTIONS route for namespaced endpoints that' do
|
790
|
+
it 'returns a 204' do
|
791
|
+
expect(last_response.status).to be 204
|
792
|
+
end
|
793
|
+
|
794
|
+
it 'has an empty body' do
|
795
|
+
expect(last_response.body).to be_blank
|
796
|
+
end
|
797
|
+
|
798
|
+
it 'has an Allow header' do
|
799
|
+
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
|
800
|
+
end
|
801
|
+
end
|
802
|
+
end
|
803
|
+
|
752
804
|
describe 'adds an OPTIONS route for namespaced endpoints that' do
|
753
805
|
before do
|
754
806
|
subject.before { header 'X-Custom-Header', 'foo' }
|
755
807
|
subject.namespace :example do
|
756
808
|
before { header 'X-Custom-Header-2', 'foo' }
|
809
|
+
|
757
810
|
get :inner do
|
758
811
|
'example/inner'
|
759
812
|
end
|
@@ -762,7 +815,7 @@ XML
|
|
762
815
|
end
|
763
816
|
|
764
817
|
it 'returns a 204' do
|
765
|
-
expect(last_response.status).to
|
818
|
+
expect(last_response.status).to be 204
|
766
819
|
end
|
767
820
|
|
768
821
|
it 'has an empty body' do
|
@@ -800,7 +853,7 @@ XML
|
|
800
853
|
end
|
801
854
|
|
802
855
|
it 'returns a 405' do
|
803
|
-
expect(last_response.status).to
|
856
|
+
expect(last_response.status).to be 405
|
804
857
|
end
|
805
858
|
|
806
859
|
it 'contains error message in body' do
|
@@ -816,7 +869,7 @@ XML
|
|
816
869
|
end
|
817
870
|
end
|
818
871
|
|
819
|
-
describe 'when hook behaviour is controlled by attributes on the route
|
872
|
+
describe 'when hook behaviour is controlled by attributes on the route' do
|
820
873
|
before do
|
821
874
|
subject.before do
|
822
875
|
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
|
@@ -839,28 +892,31 @@ XML
|
|
839
892
|
let(:response) { delete('/example') }
|
840
893
|
|
841
894
|
it 'responds with a 405 status' do
|
842
|
-
expect(response.status).to
|
895
|
+
expect(response.status).to be 405
|
843
896
|
end
|
844
897
|
end
|
845
898
|
|
846
899
|
context 'when HTTP method is defined with attribute' do
|
847
900
|
let(:response) { post('/example?secret=incorrect_password') }
|
901
|
+
|
848
902
|
it 'responds with the defined error in the before hook' do
|
849
|
-
expect(response.status).to
|
903
|
+
expect(response.status).to be 401
|
850
904
|
end
|
851
905
|
end
|
852
906
|
|
853
907
|
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
|
854
908
|
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
|
909
|
+
|
855
910
|
it 'ends up in the endpoint' do
|
856
|
-
expect(response.status).to
|
911
|
+
expect(response.status).to be 401
|
857
912
|
end
|
858
913
|
end
|
859
914
|
|
860
915
|
context 'when HTTP method is defined and everything is like the before hooks expect' do
|
861
916
|
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
|
917
|
+
|
862
918
|
it 'ends up in the endpoint' do
|
863
|
-
expect(response.status).to
|
919
|
+
expect(response.status).to be 201
|
864
920
|
end
|
865
921
|
end
|
866
922
|
|
@@ -868,7 +924,7 @@ XML
|
|
868
924
|
let(:response) { head('/example?id=504') }
|
869
925
|
|
870
926
|
it 'responds with 401 because before expectations in before hooks are not met' do
|
871
|
-
expect(response.status).to
|
927
|
+
expect(response.status).to be 401
|
872
928
|
end
|
873
929
|
end
|
874
930
|
|
@@ -876,7 +932,7 @@ XML
|
|
876
932
|
let(:response) { head('/example?id=504&secret=password') }
|
877
933
|
|
878
934
|
it 'responds with 200 because before hooks are not called' do
|
879
|
-
expect(response.status).to
|
935
|
+
expect(response.status).to be 200
|
880
936
|
end
|
881
937
|
end
|
882
938
|
end
|
@@ -893,7 +949,7 @@ XML
|
|
893
949
|
end
|
894
950
|
|
895
951
|
it 'returns a 200' do
|
896
|
-
expect(last_response.status).to
|
952
|
+
expect(last_response.status).to be 200
|
897
953
|
end
|
898
954
|
|
899
955
|
it 'has an empty body' do
|
@@ -909,31 +965,33 @@ XML
|
|
909
965
|
'example'
|
910
966
|
end
|
911
967
|
head '/example'
|
912
|
-
expect(last_response.status).to
|
968
|
+
expect(last_response.status).to be 400
|
913
969
|
end
|
914
970
|
end
|
915
971
|
|
916
972
|
context 'do_not_route_head!' do
|
917
|
-
before
|
973
|
+
before do
|
918
974
|
subject.do_not_route_head!
|
919
975
|
subject.get 'example' do
|
920
976
|
'example'
|
921
977
|
end
|
922
978
|
end
|
979
|
+
|
923
980
|
it 'options does not contain HEAD' do
|
924
981
|
options '/example'
|
925
|
-
expect(last_response.status).to
|
982
|
+
expect(last_response.status).to be 204
|
926
983
|
expect(last_response.body).to eql ''
|
927
984
|
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'
|
928
985
|
end
|
986
|
+
|
929
987
|
it 'does not allow HEAD on a GET request' do
|
930
988
|
head '/example'
|
931
|
-
expect(last_response.status).to
|
989
|
+
expect(last_response.status).to be 405
|
932
990
|
end
|
933
991
|
end
|
934
992
|
|
935
993
|
context 'do_not_route_options!' do
|
936
|
-
before
|
994
|
+
before do
|
937
995
|
subject.do_not_route_options!
|
938
996
|
subject.get 'example' do
|
939
997
|
'example'
|
@@ -942,19 +1000,19 @@ XML
|
|
942
1000
|
|
943
1001
|
it 'does not create an OPTIONS route' do
|
944
1002
|
options '/example'
|
945
|
-
expect(last_response.status).to
|
1003
|
+
expect(last_response.status).to be 405
|
946
1004
|
end
|
947
1005
|
|
948
1006
|
it 'does not include OPTIONS in Allow header' do
|
949
1007
|
options '/example'
|
950
|
-
expect(last_response.status).to
|
1008
|
+
expect(last_response.status).to be 405
|
951
1009
|
expect(last_response.headers['Allow']).to eql 'GET, HEAD'
|
952
1010
|
end
|
953
1011
|
end
|
954
1012
|
|
955
1013
|
describe '.compile!' do
|
956
1014
|
it 'requires the grape/eager_load file' do
|
957
|
-
expect(app).to receive(:require).with('grape/eager_load')
|
1015
|
+
expect(app).to receive(:require).with('grape/eager_load').and_return(nil)
|
958
1016
|
app.compile!
|
959
1017
|
end
|
960
1018
|
|
@@ -976,7 +1034,7 @@ XML
|
|
976
1034
|
context 'when the app was mounted' do
|
977
1035
|
it 'returns the first mounted instance' do
|
978
1036
|
mounted_app = app
|
979
|
-
Class.new(
|
1037
|
+
Class.new(described_class) do
|
980
1038
|
namespace 'new_namespace' do
|
981
1039
|
mount mounted_app
|
982
1040
|
end
|
@@ -1004,6 +1062,7 @@ XML
|
|
1004
1062
|
end
|
1005
1063
|
subject.namespace :blah do
|
1006
1064
|
before { @foo = 'foo' }
|
1065
|
+
|
1007
1066
|
get '/' do
|
1008
1067
|
"blah - #{@foo}"
|
1009
1068
|
end
|
@@ -1045,7 +1104,7 @@ XML
|
|
1045
1104
|
@var ||= 'default'
|
1046
1105
|
end
|
1047
1106
|
|
1048
|
-
expect(m).to receive(:do_something!).
|
1107
|
+
expect(m).to receive(:do_something!).twice
|
1049
1108
|
get '/'
|
1050
1109
|
expect(last_response.body).to eql 'default'
|
1051
1110
|
end
|
@@ -1061,21 +1120,23 @@ XML
|
|
1061
1120
|
end
|
1062
1121
|
subject.resource ':id' do
|
1063
1122
|
before { a.do_something! }
|
1123
|
+
|
1064
1124
|
before_validation { b.do_something! }
|
1065
1125
|
after_validation { c.do_something! }
|
1066
1126
|
after { d.do_something! }
|
1127
|
+
|
1067
1128
|
get do
|
1068
1129
|
'got it'
|
1069
1130
|
end
|
1070
1131
|
end
|
1071
1132
|
|
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!).
|
1133
|
+
expect(a).to receive(:do_something!).once
|
1134
|
+
expect(b).to receive(:do_something!).once
|
1135
|
+
expect(c).to receive(:do_something!).once
|
1136
|
+
expect(d).to receive(:do_something!).once
|
1076
1137
|
|
1077
1138
|
get '/123'
|
1078
|
-
expect(last_response.status).to
|
1139
|
+
expect(last_response.status).to be 200
|
1079
1140
|
expect(last_response.body).to eql 'got it'
|
1080
1141
|
end
|
1081
1142
|
|
@@ -1090,21 +1151,23 @@ XML
|
|
1090
1151
|
end
|
1091
1152
|
subject.resource ':id' do
|
1092
1153
|
before { a.do_something! }
|
1154
|
+
|
1093
1155
|
before_validation { b.do_something! }
|
1094
1156
|
after_validation { c.do_something! }
|
1095
1157
|
after { d.do_something! }
|
1158
|
+
|
1096
1159
|
get do
|
1097
1160
|
'got it'
|
1098
1161
|
end
|
1099
1162
|
end
|
1100
1163
|
|
1101
|
-
expect(a).to receive(:do_something!).
|
1102
|
-
expect(b).to receive(:do_something!).
|
1164
|
+
expect(a).to receive(:do_something!).once
|
1165
|
+
expect(b).to receive(:do_something!).once
|
1103
1166
|
expect(c).to receive(:do_something!).exactly(0).times
|
1104
1167
|
expect(d).to receive(:do_something!).exactly(0).times
|
1105
1168
|
|
1106
1169
|
get '/abc'
|
1107
|
-
expect(last_response.status).to
|
1170
|
+
expect(last_response.status).to be 400
|
1108
1171
|
expect(last_response.body).to eql 'id is invalid'
|
1109
1172
|
end
|
1110
1173
|
|
@@ -1120,21 +1183,23 @@ XML
|
|
1120
1183
|
end
|
1121
1184
|
subject.resource ':id' do
|
1122
1185
|
before { a.here(i += 1) }
|
1186
|
+
|
1123
1187
|
before_validation { b.here(i += 1) }
|
1124
1188
|
after_validation { c.here(i += 1) }
|
1125
1189
|
after { d.here(i += 1) }
|
1190
|
+
|
1126
1191
|
get do
|
1127
1192
|
'got it'
|
1128
1193
|
end
|
1129
1194
|
end
|
1130
1195
|
|
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).
|
1196
|
+
expect(a).to receive(:here).with(1).once
|
1197
|
+
expect(b).to receive(:here).with(2).once
|
1198
|
+
expect(c).to receive(:here).with(3).once
|
1199
|
+
expect(d).to receive(:here).with(4).once
|
1135
1200
|
|
1136
1201
|
get '/123'
|
1137
|
-
expect(last_response.status).to
|
1202
|
+
expect(last_response.status).to be 200
|
1138
1203
|
expect(last_response.body).to eql 'got it'
|
1139
1204
|
end
|
1140
1205
|
end
|
@@ -1151,7 +1216,7 @@ XML
|
|
1151
1216
|
|
1152
1217
|
it 'does not set Cache-Control' do
|
1153
1218
|
get '/foo'
|
1154
|
-
expect(last_response.headers['Cache-Control']).to
|
1219
|
+
expect(last_response.headers['Cache-Control']).to be_nil
|
1155
1220
|
end
|
1156
1221
|
|
1157
1222
|
it 'sets content type for xml' do
|
@@ -1176,7 +1241,7 @@ XML
|
|
1176
1241
|
|
1177
1242
|
it 'returns raw data when content type binary' do
|
1178
1243
|
image_filename = 'grape.png'
|
1179
|
-
file = File.
|
1244
|
+
file = File.binread(image_filename)
|
1180
1245
|
subject.format :binary
|
1181
1246
|
subject.get('/binary_file') { File.binread(image_filename) }
|
1182
1247
|
get '/binary_file'
|
@@ -1208,7 +1273,7 @@ XML
|
|
1208
1273
|
get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
|
1209
1274
|
|
1210
1275
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1211
|
-
expect(last_response.headers['Content-Length']).to
|
1276
|
+
expect(last_response.headers['Content-Length']).to be_nil
|
1212
1277
|
expect(last_response.headers['Cache-Control']).to eq('no-cache')
|
1213
1278
|
expect(last_response.headers['Transfer-Encoding']).to eq('chunked')
|
1214
1279
|
|
@@ -1225,7 +1290,7 @@ XML
|
|
1225
1290
|
subject.format :json
|
1226
1291
|
subject.get('/error') { error!('error in json', 500) }
|
1227
1292
|
get '/error.json'
|
1228
|
-
expect(last_response.status).to
|
1293
|
+
expect(last_response.status).to be 500
|
1229
1294
|
expect(last_response.headers['Content-Type']).to eql 'application/json'
|
1230
1295
|
end
|
1231
1296
|
|
@@ -1233,7 +1298,7 @@ XML
|
|
1233
1298
|
subject.format :xml
|
1234
1299
|
subject.get('/error') { error!('error in xml', 500) }
|
1235
1300
|
get '/error'
|
1236
|
-
expect(last_response.status).to
|
1301
|
+
expect(last_response.status).to be 500
|
1237
1302
|
expect(last_response.headers['Content-Type']).to eql 'application/xml'
|
1238
1303
|
end
|
1239
1304
|
|
@@ -1492,9 +1557,9 @@ XML
|
|
1492
1557
|
end
|
1493
1558
|
subject.get(:hello) { 'Hello, world.' }
|
1494
1559
|
get '/hello'
|
1495
|
-
expect(last_response.status).to
|
1560
|
+
expect(last_response.status).to be 401
|
1496
1561
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1497
|
-
expect(last_response.status).to
|
1562
|
+
expect(last_response.status).to be 200
|
1498
1563
|
end
|
1499
1564
|
|
1500
1565
|
it 'is scopable' do
|
@@ -1508,9 +1573,9 @@ XML
|
|
1508
1573
|
end
|
1509
1574
|
|
1510
1575
|
get '/hello'
|
1511
|
-
expect(last_response.status).to
|
1576
|
+
expect(last_response.status).to be 200
|
1512
1577
|
get '/admin/hello'
|
1513
|
-
expect(last_response.status).to
|
1578
|
+
expect(last_response.status).to be 401
|
1514
1579
|
end
|
1515
1580
|
|
1516
1581
|
it 'is callable via .auth as well' do
|
@@ -1520,9 +1585,9 @@ XML
|
|
1520
1585
|
|
1521
1586
|
subject.get(:hello) { 'Hello, world.' }
|
1522
1587
|
get '/hello'
|
1523
|
-
expect(last_response.status).to
|
1588
|
+
expect(last_response.status).to be 401
|
1524
1589
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1525
|
-
expect(last_response.status).to
|
1590
|
+
expect(last_response.status).to be 200
|
1526
1591
|
end
|
1527
1592
|
|
1528
1593
|
it 'has access to the current endpoint' do
|
@@ -1552,9 +1617,9 @@ XML
|
|
1552
1617
|
|
1553
1618
|
subject.get(:hello) { 'Hello, world.' }
|
1554
1619
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1555
|
-
expect(last_response.status).to
|
1620
|
+
expect(last_response.status).to be 200
|
1556
1621
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
|
1557
|
-
expect(last_response.status).to
|
1622
|
+
expect(last_response.status).to be 401
|
1558
1623
|
end
|
1559
1624
|
|
1560
1625
|
it 'can set instance variables accessible to routes' do
|
@@ -1566,44 +1631,36 @@ XML
|
|
1566
1631
|
|
1567
1632
|
subject.get(:hello) { @hello }
|
1568
1633
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1569
|
-
expect(last_response.status).to
|
1634
|
+
expect(last_response.status).to be 200
|
1570
1635
|
expect(last_response.body).to eql 'Hello, world.'
|
1571
1636
|
end
|
1572
1637
|
end
|
1573
1638
|
|
1574
1639
|
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
1640
|
it 'returns an instance of Logger class by default' do
|
1585
1641
|
expect(subject.logger.class).to eql Logger
|
1586
1642
|
end
|
1587
1643
|
|
1588
|
-
|
1589
|
-
|
1590
|
-
|
1591
|
-
|
1592
|
-
|
1593
|
-
|
1644
|
+
context 'with a custom logger' do
|
1645
|
+
subject do
|
1646
|
+
Class.new(described_class) do
|
1647
|
+
def self.io
|
1648
|
+
@io ||= StringIO.new
|
1649
|
+
end
|
1650
|
+
logger ::Logger.new(io)
|
1651
|
+
end
|
1652
|
+
end
|
1594
1653
|
|
1595
|
-
|
1596
|
-
|
1597
|
-
|
1598
|
-
|
1599
|
-
|
1600
|
-
expect(subject.io).to receive(:write).with(message)
|
1601
|
-
subject.logger.info 'this will be logged'
|
1654
|
+
it 'exposes its interaface' do
|
1655
|
+
message = 'this will be logged'
|
1656
|
+
subject.logger.info message
|
1657
|
+
expect(subject.io.string).to include(message)
|
1658
|
+
end
|
1602
1659
|
end
|
1603
1660
|
|
1604
1661
|
it 'does not unnecessarily retain duplicate setup blocks' do
|
1605
1662
|
subject.logger
|
1606
|
-
expect { subject.logger }.
|
1663
|
+
expect { subject.logger }.not_to change(subject.instance_variable_get(:@setup), :size)
|
1607
1664
|
end
|
1608
1665
|
end
|
1609
1666
|
|
@@ -1729,13 +1786,13 @@ XML
|
|
1729
1786
|
end
|
1730
1787
|
|
1731
1788
|
get '/new/abc'
|
1732
|
-
expect(last_response.status).to
|
1789
|
+
expect(last_response.status).to be 404
|
1733
1790
|
get '/legacy/abc'
|
1734
|
-
expect(last_response.status).to
|
1791
|
+
expect(last_response.status).to be 200
|
1735
1792
|
get '/legacy/def'
|
1736
|
-
expect(last_response.status).to
|
1793
|
+
expect(last_response.status).to be 404
|
1737
1794
|
get '/new/def'
|
1738
|
-
expect(last_response.status).to
|
1795
|
+
expect(last_response.status).to be 200
|
1739
1796
|
end
|
1740
1797
|
end
|
1741
1798
|
|
@@ -1955,8 +2012,8 @@ XML
|
|
1955
2012
|
end
|
1956
2013
|
|
1957
2014
|
context 'with multiple apis' do
|
1958
|
-
let(:a) { Class.new(
|
1959
|
-
let(:b) { Class.new(
|
2015
|
+
let(:a) { Class.new(described_class) }
|
2016
|
+
let(:b) { Class.new(described_class) }
|
1960
2017
|
|
1961
2018
|
before do
|
1962
2019
|
a.helpers do
|
@@ -1990,7 +2047,7 @@ XML
|
|
1990
2047
|
raise 'rain!'
|
1991
2048
|
end
|
1992
2049
|
get '/exception'
|
1993
|
-
expect(last_response.status).to
|
2050
|
+
expect(last_response.status).to be 500
|
1994
2051
|
expect(last_response.body).to eq 'rain!'
|
1995
2052
|
end
|
1996
2053
|
|
@@ -2002,7 +2059,7 @@ XML
|
|
2002
2059
|
raise 'rain!'
|
2003
2060
|
end
|
2004
2061
|
get '/exception'
|
2005
|
-
expect(last_response.status).to
|
2062
|
+
expect(last_response.status).to be 500
|
2006
2063
|
expect(last_response.body).to eq({ error: 'rain!' }.to_json)
|
2007
2064
|
end
|
2008
2065
|
|
@@ -2012,7 +2069,7 @@ XML
|
|
2012
2069
|
subject.get('/unrescued') { raise 'beefcake' }
|
2013
2070
|
|
2014
2071
|
get '/rescued'
|
2015
|
-
expect(last_response.status).to
|
2072
|
+
expect(last_response.status).to be 500
|
2016
2073
|
|
2017
2074
|
expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
|
2018
2075
|
end
|
@@ -2031,10 +2088,10 @@ XML
|
|
2031
2088
|
subject.get('/standard_error') { raise StandardError }
|
2032
2089
|
|
2033
2090
|
get '/child_of_standard_error'
|
2034
|
-
expect(last_response.status).to
|
2091
|
+
expect(last_response.status).to be 402
|
2035
2092
|
|
2036
2093
|
get '/standard_error'
|
2037
|
-
expect(last_response.status).to
|
2094
|
+
expect(last_response.status).to be 401
|
2038
2095
|
end
|
2039
2096
|
|
2040
2097
|
context 'CustomError subclass of Grape::Exceptions::Base' do
|
@@ -2075,7 +2132,7 @@ XML
|
|
2075
2132
|
subject.get('/formatter_exception') { 'Hello world' }
|
2076
2133
|
|
2077
2134
|
get '/formatter_exception'
|
2078
|
-
expect(last_response.status).to
|
2135
|
+
expect(last_response.status).to be 500
|
2079
2136
|
expect(last_response.body).to eq('Formatter Error')
|
2080
2137
|
end
|
2081
2138
|
|
@@ -2085,7 +2142,7 @@ XML
|
|
2085
2142
|
|
2086
2143
|
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
|
2087
2144
|
get '/'
|
2088
|
-
expect(last_response.status).to
|
2145
|
+
expect(last_response.status).to be 500
|
2089
2146
|
expect(last_response.body).to eql 'Invalid response'
|
2090
2147
|
end
|
2091
2148
|
end
|
@@ -2099,14 +2156,16 @@ XML
|
|
2099
2156
|
raise 'rain!'
|
2100
2157
|
end
|
2101
2158
|
get '/exception'
|
2102
|
-
expect(last_response.status).to
|
2159
|
+
expect(last_response.status).to be 202
|
2103
2160
|
expect(last_response.body).to eq('rescued from rain!')
|
2104
2161
|
end
|
2105
2162
|
|
2106
2163
|
context 'custom errors' do
|
2107
2164
|
before do
|
2108
2165
|
class ConnectionError < RuntimeError; end
|
2166
|
+
|
2109
2167
|
class DatabaseError < RuntimeError; end
|
2168
|
+
|
2110
2169
|
class CommunicationError < StandardError; end
|
2111
2170
|
end
|
2112
2171
|
|
@@ -2118,9 +2177,10 @@ XML
|
|
2118
2177
|
raise ConnectionError
|
2119
2178
|
end
|
2120
2179
|
get '/exception'
|
2121
|
-
expect(last_response.status).to
|
2180
|
+
expect(last_response.status).to be 500
|
2122
2181
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2123
2182
|
end
|
2183
|
+
|
2124
2184
|
it 'rescues a specific error' do
|
2125
2185
|
subject.rescue_from ConnectionError do |e|
|
2126
2186
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2129,9 +2189,10 @@ XML
|
|
2129
2189
|
raise ConnectionError
|
2130
2190
|
end
|
2131
2191
|
get '/exception'
|
2132
|
-
expect(last_response.status).to
|
2192
|
+
expect(last_response.status).to be 500
|
2133
2193
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2134
2194
|
end
|
2195
|
+
|
2135
2196
|
it 'rescues a subclass of an error by default' do
|
2136
2197
|
subject.rescue_from RuntimeError do |e|
|
2137
2198
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2140,9 +2201,10 @@ XML
|
|
2140
2201
|
raise ConnectionError
|
2141
2202
|
end
|
2142
2203
|
get '/exception'
|
2143
|
-
expect(last_response.status).to
|
2204
|
+
expect(last_response.status).to be 500
|
2144
2205
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2145
2206
|
end
|
2207
|
+
|
2146
2208
|
it 'rescues multiple specific errors' do
|
2147
2209
|
subject.rescue_from ConnectionError do |e|
|
2148
2210
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2157,12 +2219,13 @@ XML
|
|
2157
2219
|
raise DatabaseError
|
2158
2220
|
end
|
2159
2221
|
get '/connection'
|
2160
|
-
expect(last_response.status).to
|
2222
|
+
expect(last_response.status).to be 500
|
2161
2223
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2162
2224
|
get '/database'
|
2163
|
-
expect(last_response.status).to
|
2225
|
+
expect(last_response.status).to be 500
|
2164
2226
|
expect(last_response.body).to eq('rescued from DatabaseError')
|
2165
2227
|
end
|
2228
|
+
|
2166
2229
|
it 'does not rescue a different error' do
|
2167
2230
|
subject.rescue_from RuntimeError do |e|
|
2168
2231
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2228,7 +2291,7 @@ XML
|
|
2228
2291
|
subject.rescue_from :all, with: :not_exist_method
|
2229
2292
|
subject.get('/rescue_method') { raise StandardError }
|
2230
2293
|
|
2231
|
-
expect { get '/rescue_method' }.to raise_error(NoMethodError,
|
2294
|
+
expect { get '/rescue_method' }.to raise_error(NoMethodError, /^undefined method 'not_exist_method'/)
|
2232
2295
|
end
|
2233
2296
|
|
2234
2297
|
it 'correctly chooses exception handler if :all handler is specified' do
|
@@ -2262,6 +2325,7 @@ XML
|
|
2262
2325
|
module ApiSpec
|
2263
2326
|
module APIErrors
|
2264
2327
|
class ParentError < StandardError; end
|
2328
|
+
|
2265
2329
|
class ChildError < ParentError; end
|
2266
2330
|
end
|
2267
2331
|
end
|
@@ -2282,9 +2346,9 @@ XML
|
|
2282
2346
|
end
|
2283
2347
|
|
2284
2348
|
get '/caught_child'
|
2285
|
-
expect(last_response.status).to
|
2349
|
+
expect(last_response.status).to be 500
|
2286
2350
|
get '/caught_parent'
|
2287
|
-
expect(last_response.status).to
|
2351
|
+
expect(last_response.status).to be 500
|
2288
2352
|
expect { get '/uncaught_parent' }.to raise_error(StandardError)
|
2289
2353
|
end
|
2290
2354
|
|
@@ -2297,7 +2361,7 @@ XML
|
|
2297
2361
|
end
|
2298
2362
|
|
2299
2363
|
get '/caught_child'
|
2300
|
-
expect(last_response.status).to
|
2364
|
+
expect(last_response.status).to be 500
|
2301
2365
|
end
|
2302
2366
|
|
2303
2367
|
it 'does not rescue child errors if rescue_subclasses is false' do
|
@@ -2392,7 +2456,7 @@ XML
|
|
2392
2456
|
end
|
2393
2457
|
|
2394
2458
|
context 'class' do
|
2395
|
-
before
|
2459
|
+
before do
|
2396
2460
|
module ApiSpec
|
2397
2461
|
class CustomErrorFormatter
|
2398
2462
|
def self.call(message, _backtrace, _options, _env, _original_exception)
|
@@ -2401,6 +2465,7 @@ XML
|
|
2401
2465
|
end
|
2402
2466
|
end
|
2403
2467
|
end
|
2468
|
+
|
2404
2469
|
it 'returns a custom error format' do
|
2405
2470
|
subject.rescue_from :all, backtrace: true
|
2406
2471
|
subject.error_formatter :txt, ApiSpec::CustomErrorFormatter
|
@@ -2414,7 +2479,7 @@ XML
|
|
2414
2479
|
|
2415
2480
|
describe 'with' do
|
2416
2481
|
context 'class' do
|
2417
|
-
before
|
2482
|
+
before do
|
2418
2483
|
module ApiSpec
|
2419
2484
|
class CustomErrorFormatter
|
2420
2485
|
def self.call(message, _backtrace, _option, _env, _original_exception)
|
@@ -2444,6 +2509,7 @@ XML
|
|
2444
2509
|
get '/exception'
|
2445
2510
|
expect(last_response.body).to eql '{"error":"rain!"}'
|
2446
2511
|
end
|
2512
|
+
|
2447
2513
|
it 'rescues all errors and return :json with backtrace' do
|
2448
2514
|
subject.rescue_from :all, backtrace: true
|
2449
2515
|
subject.format :json
|
@@ -2455,6 +2521,7 @@ XML
|
|
2455
2521
|
expect(json['error']).to eql 'rain!'
|
2456
2522
|
expect(json['backtrace'].length).to be > 0
|
2457
2523
|
end
|
2524
|
+
|
2458
2525
|
it 'rescues error! and return txt' do
|
2459
2526
|
subject.format :txt
|
2460
2527
|
subject.get '/error' do
|
@@ -2463,23 +2530,26 @@ XML
|
|
2463
2530
|
get '/error'
|
2464
2531
|
expect(last_response.body).to eql 'Access Denied'
|
2465
2532
|
end
|
2533
|
+
|
2466
2534
|
context 'with json format' do
|
2467
2535
|
before { subject.format :json }
|
2468
2536
|
|
2537
|
+
after do
|
2538
|
+
get '/error'
|
2539
|
+
expect(last_response.body).to eql('{"error":"failure"}')
|
2540
|
+
end
|
2541
|
+
|
2469
2542
|
it 'rescues error! called with a string and returns json' do
|
2470
2543
|
subject.get('/error') { error!(:failure, 401) }
|
2471
2544
|
end
|
2545
|
+
|
2472
2546
|
it 'rescues error! called with a symbol and returns json' do
|
2473
2547
|
subject.get('/error') { error!(:failure, 401) }
|
2474
2548
|
end
|
2549
|
+
|
2475
2550
|
it 'rescues error! called with a hash and returns json' do
|
2476
2551
|
subject.get('/error') { error!({ error: :failure }, 401) }
|
2477
2552
|
end
|
2478
|
-
|
2479
|
-
after do
|
2480
|
-
get '/error'
|
2481
|
-
expect(last_response.body).to eql('{"error":"failure"}')
|
2482
|
-
end
|
2483
2553
|
end
|
2484
2554
|
end
|
2485
2555
|
|
@@ -2492,6 +2562,7 @@ XML
|
|
2492
2562
|
get '/excel.xls'
|
2493
2563
|
expect(last_response.content_type).to eq('application/vnd.ms-excel')
|
2494
2564
|
end
|
2565
|
+
|
2495
2566
|
it 'allows to override content-type' do
|
2496
2567
|
subject.get :content do
|
2497
2568
|
content_type 'text/javascript'
|
@@ -2500,6 +2571,7 @@ XML
|
|
2500
2571
|
get '/content'
|
2501
2572
|
expect(last_response.content_type).to eq('text/javascript')
|
2502
2573
|
end
|
2574
|
+
|
2503
2575
|
it 'removes existing content types' do
|
2504
2576
|
subject.content_type :xls, 'application/vnd.ms-excel'
|
2505
2577
|
subject.get :excel do
|
@@ -2517,24 +2589,27 @@ XML
|
|
2517
2589
|
|
2518
2590
|
describe '.formatter' do
|
2519
2591
|
context 'multiple formatters' do
|
2520
|
-
before
|
2592
|
+
before do
|
2521
2593
|
subject.formatter :json, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
2522
2594
|
subject.formatter :txt, ->(object, _env) { "custom_formatter: #{object[:some]}" }
|
2523
2595
|
subject.get :simple do
|
2524
2596
|
{ some: 'hash' }
|
2525
2597
|
end
|
2526
2598
|
end
|
2599
|
+
|
2527
2600
|
it 'sets one formatter' do
|
2528
2601
|
get '/simple.json'
|
2529
2602
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2530
2603
|
end
|
2604
|
+
|
2531
2605
|
it 'sets another formatter' do
|
2532
2606
|
get '/simple.txt'
|
2533
2607
|
expect(last_response.body).to eql 'custom_formatter: hash'
|
2534
2608
|
end
|
2535
2609
|
end
|
2610
|
+
|
2536
2611
|
context 'custom formatter' do
|
2537
|
-
before
|
2612
|
+
before do
|
2538
2613
|
subject.content_type :json, 'application/json'
|
2539
2614
|
subject.content_type :custom, 'application/custom'
|
2540
2615
|
subject.formatter :custom, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
@@ -2542,15 +2617,18 @@ XML
|
|
2542
2617
|
{ some: 'hash' }
|
2543
2618
|
end
|
2544
2619
|
end
|
2620
|
+
|
2545
2621
|
it 'uses json' do
|
2546
2622
|
get '/simple.json'
|
2547
2623
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2548
2624
|
end
|
2625
|
+
|
2549
2626
|
it 'uses custom formatter' do
|
2550
2627
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2551
2628
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2552
2629
|
end
|
2553
2630
|
end
|
2631
|
+
|
2554
2632
|
context 'custom formatter class' do
|
2555
2633
|
module ApiSpec
|
2556
2634
|
module CustomFormatter
|
@@ -2559,7 +2637,7 @@ XML
|
|
2559
2637
|
end
|
2560
2638
|
end
|
2561
2639
|
end
|
2562
|
-
before
|
2640
|
+
before do
|
2563
2641
|
subject.content_type :json, 'application/json'
|
2564
2642
|
subject.content_type :custom, 'application/custom'
|
2565
2643
|
subject.formatter :custom, ApiSpec::CustomFormatter
|
@@ -2567,10 +2645,12 @@ XML
|
|
2567
2645
|
{ some: 'hash' }
|
2568
2646
|
end
|
2569
2647
|
end
|
2648
|
+
|
2570
2649
|
it 'uses json' do
|
2571
2650
|
get '/simple.json'
|
2572
2651
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2573
2652
|
end
|
2653
|
+
|
2574
2654
|
it 'uses custom formatter' do
|
2575
2655
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2576
2656
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
@@ -2588,8 +2668,9 @@ XML
|
|
2588
2668
|
expect(last_response.status).to eq(201)
|
2589
2669
|
expect(last_response.body).to eq('{"x":42}')
|
2590
2670
|
end
|
2671
|
+
|
2591
2672
|
context 'lambda parser' do
|
2592
|
-
before
|
2673
|
+
before do
|
2593
2674
|
subject.content_type :txt, 'text/plain'
|
2594
2675
|
subject.content_type :custom, 'text/custom'
|
2595
2676
|
subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }
|
@@ -2597,6 +2678,7 @@ XML
|
|
2597
2678
|
params[:simple]
|
2598
2679
|
end
|
2599
2680
|
end
|
2681
|
+
|
2600
2682
|
['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
|
2601
2683
|
it "uses parser for #{content_type}" do
|
2602
2684
|
put '/simple', 'simple', 'CONTENT_TYPE' => content_type
|
@@ -2605,6 +2687,7 @@ XML
|
|
2605
2687
|
end
|
2606
2688
|
end
|
2607
2689
|
end
|
2690
|
+
|
2608
2691
|
context 'custom parser class' do
|
2609
2692
|
module ApiSpec
|
2610
2693
|
module CustomParser
|
@@ -2613,7 +2696,7 @@ XML
|
|
2613
2696
|
end
|
2614
2697
|
end
|
2615
2698
|
end
|
2616
|
-
before
|
2699
|
+
before do
|
2617
2700
|
subject.content_type :txt, 'text/plain'
|
2618
2701
|
subject.content_type :custom, 'text/custom'
|
2619
2702
|
subject.parser :custom, ApiSpec::CustomParser
|
@@ -2621,12 +2704,14 @@ XML
|
|
2621
2704
|
params[:simple]
|
2622
2705
|
end
|
2623
2706
|
end
|
2707
|
+
|
2624
2708
|
it 'uses custom parser' do
|
2625
2709
|
put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
|
2626
2710
|
expect(last_response.status).to eq(200)
|
2627
2711
|
expect(last_response.body).to eql 'elpmis'
|
2628
2712
|
end
|
2629
2713
|
end
|
2714
|
+
|
2630
2715
|
if Object.const_defined? :MultiXml
|
2631
2716
|
context 'multi_xml' do
|
2632
2717
|
it "doesn't parse yaml" do
|
@@ -2651,12 +2736,13 @@ XML
|
|
2651
2736
|
end
|
2652
2737
|
end
|
2653
2738
|
context 'none parser class' do
|
2654
|
-
before
|
2739
|
+
before do
|
2655
2740
|
subject.parser :json, nil
|
2656
2741
|
subject.put 'data' do
|
2657
2742
|
"body: #{env['api.request.body']}"
|
2658
2743
|
end
|
2659
2744
|
end
|
2745
|
+
|
2660
2746
|
it 'does not parse data' do
|
2661
2747
|
put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
|
2662
2748
|
expect(last_response.status).to eq(200)
|
@@ -2666,10 +2752,11 @@ XML
|
|
2666
2752
|
end
|
2667
2753
|
|
2668
2754
|
describe '.default_format' do
|
2669
|
-
before
|
2755
|
+
before do
|
2670
2756
|
subject.format :json
|
2671
2757
|
subject.default_format :json
|
2672
2758
|
end
|
2759
|
+
|
2673
2760
|
it 'returns data in default format' do
|
2674
2761
|
subject.get '/data' do
|
2675
2762
|
{ x: 42 }
|
@@ -2678,6 +2765,7 @@ XML
|
|
2678
2765
|
expect(last_response.status).to eq(200)
|
2679
2766
|
expect(last_response.body).to eq('{"x":42}')
|
2680
2767
|
end
|
2768
|
+
|
2681
2769
|
it 'parses data in default format' do
|
2682
2770
|
subject.post '/data' do
|
2683
2771
|
{ x: params[:x] }
|
@@ -2696,16 +2784,18 @@ XML
|
|
2696
2784
|
raise 'rain!'
|
2697
2785
|
end
|
2698
2786
|
get '/exception'
|
2699
|
-
expect(last_response.status).to
|
2787
|
+
expect(last_response.status).to be 200
|
2700
2788
|
end
|
2789
|
+
|
2701
2790
|
it 'has a default error status' do
|
2702
2791
|
subject.rescue_from :all
|
2703
2792
|
subject.get '/exception' do
|
2704
2793
|
raise 'rain!'
|
2705
2794
|
end
|
2706
2795
|
get '/exception'
|
2707
|
-
expect(last_response.status).to
|
2796
|
+
expect(last_response.status).to be 500
|
2708
2797
|
end
|
2798
|
+
|
2709
2799
|
it 'uses the default error status in error!' do
|
2710
2800
|
subject.rescue_from :all
|
2711
2801
|
subject.default_error_status 400
|
@@ -2713,7 +2803,7 @@ XML
|
|
2713
2803
|
error! 'rain!'
|
2714
2804
|
end
|
2715
2805
|
get '/exception'
|
2716
|
-
expect(last_response.status).to
|
2806
|
+
expect(last_response.status).to be 400
|
2717
2807
|
end
|
2718
2808
|
end
|
2719
2809
|
|
@@ -2739,7 +2829,7 @@ XML
|
|
2739
2829
|
end
|
2740
2830
|
|
2741
2831
|
get '/exception'
|
2742
|
-
expect(last_response.status).to
|
2832
|
+
expect(last_response.status).to be 408
|
2743
2833
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2744
2834
|
end
|
2745
2835
|
|
@@ -2750,7 +2840,7 @@ XML
|
|
2750
2840
|
end
|
2751
2841
|
|
2752
2842
|
get '/exception'
|
2753
|
-
expect(last_response.status).to
|
2843
|
+
expect(last_response.status).to be 408
|
2754
2844
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2755
2845
|
end
|
2756
2846
|
end
|
@@ -2761,12 +2851,14 @@ XML
|
|
2761
2851
|
expect(subject.routes).to eq([])
|
2762
2852
|
end
|
2763
2853
|
end
|
2854
|
+
|
2764
2855
|
describe 'single method api structure' do
|
2765
|
-
before
|
2856
|
+
before do
|
2766
2857
|
subject.get :ping do
|
2767
2858
|
'pong'
|
2768
2859
|
end
|
2769
2860
|
end
|
2861
|
+
|
2770
2862
|
it 'returns one route' do
|
2771
2863
|
expect(subject.routes.size).to eq(1)
|
2772
2864
|
route = subject.routes[0]
|
@@ -2775,8 +2867,9 @@ XML
|
|
2775
2867
|
expect(route.request_method).to eq('GET')
|
2776
2868
|
end
|
2777
2869
|
end
|
2870
|
+
|
2778
2871
|
describe 'api structure with two versions and a namespace' do
|
2779
|
-
before
|
2872
|
+
before do
|
2780
2873
|
subject.version 'v1', using: :path
|
2781
2874
|
subject.get 'version' do
|
2782
2875
|
api.version
|
@@ -2792,30 +2885,37 @@ XML
|
|
2792
2885
|
end
|
2793
2886
|
end
|
2794
2887
|
end
|
2888
|
+
|
2795
2889
|
it 'returns the latest version set' do
|
2796
2890
|
expect(subject.version).to eq('v2')
|
2797
2891
|
end
|
2892
|
+
|
2798
2893
|
it 'returns versions' do
|
2799
2894
|
expect(subject.versions).to eq(%w[v1 v2])
|
2800
2895
|
end
|
2896
|
+
|
2801
2897
|
it 'sets route paths' do
|
2802
2898
|
expect(subject.routes.size).to be >= 2
|
2803
2899
|
expect(subject.routes[0].path).to eq('/:version/version(.:format)')
|
2804
2900
|
expect(subject.routes[1].path).to eq('/p/:version/n1/n2/version(.:format)')
|
2805
2901
|
end
|
2902
|
+
|
2806
2903
|
it 'sets route versions' do
|
2807
2904
|
expect(subject.routes[0].version).to eq('v1')
|
2808
2905
|
expect(subject.routes[1].version).to eq('v2')
|
2809
2906
|
end
|
2907
|
+
|
2810
2908
|
it 'sets a nested namespace' do
|
2811
2909
|
expect(subject.routes[1].namespace).to eq('/n1/n2')
|
2812
2910
|
end
|
2911
|
+
|
2813
2912
|
it 'sets prefix' do
|
2814
2913
|
expect(subject.routes[1].prefix).to eq('p')
|
2815
2914
|
end
|
2816
2915
|
end
|
2916
|
+
|
2817
2917
|
describe 'api structure with additional parameters' do
|
2818
|
-
before
|
2918
|
+
before do
|
2819
2919
|
subject.params do
|
2820
2920
|
requires :token, desc: 'a token'
|
2821
2921
|
optional :limit, desc: 'the limit'
|
@@ -2824,14 +2924,17 @@ XML
|
|
2824
2924
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
2825
2925
|
end
|
2826
2926
|
end
|
2927
|
+
|
2827
2928
|
it 'splits a string' do
|
2828
2929
|
get '/split/a,b,c.json', token: ','
|
2829
2930
|
expect(last_response.body).to eq('["a","b","c"]')
|
2830
2931
|
end
|
2932
|
+
|
2831
2933
|
it 'splits a string with limit' do
|
2832
2934
|
get '/split/a,b,c.json', token: ',', limit: '2'
|
2833
2935
|
expect(last_response.body).to eq('["a","b,c"]')
|
2834
2936
|
end
|
2937
|
+
|
2835
2938
|
it 'sets params' do
|
2836
2939
|
expect(subject.routes.map do |route|
|
2837
2940
|
{ params: route.params }
|
@@ -2846,8 +2949,9 @@ XML
|
|
2846
2949
|
]
|
2847
2950
|
end
|
2848
2951
|
end
|
2952
|
+
|
2849
2953
|
describe 'api structure with multiple apis' do
|
2850
|
-
before
|
2954
|
+
before do
|
2851
2955
|
subject.params do
|
2852
2956
|
requires :one, desc: 'a token'
|
2853
2957
|
optional :two, desc: 'the limit'
|
@@ -2862,6 +2966,7 @@ XML
|
|
2862
2966
|
subject.get 'two' do
|
2863
2967
|
end
|
2864
2968
|
end
|
2969
|
+
|
2865
2970
|
it 'sets params' do
|
2866
2971
|
expect(subject.routes.map do |route|
|
2867
2972
|
{ params: route.params }
|
@@ -2881,8 +2986,9 @@ XML
|
|
2881
2986
|
]
|
2882
2987
|
end
|
2883
2988
|
end
|
2989
|
+
|
2884
2990
|
describe 'api structure with an api without params' do
|
2885
|
-
before
|
2991
|
+
before do
|
2886
2992
|
subject.params do
|
2887
2993
|
requires :one, desc: 'a token'
|
2888
2994
|
optional :two, desc: 'the limit'
|
@@ -2893,6 +2999,7 @@ XML
|
|
2893
2999
|
subject.get 'two' do
|
2894
3000
|
end
|
2895
3001
|
end
|
3002
|
+
|
2896
3003
|
it 'sets params' do
|
2897
3004
|
expect(subject.routes.map do |route|
|
2898
3005
|
{ params: route.params }
|
@@ -2909,17 +3016,20 @@ XML
|
|
2909
3016
|
]
|
2910
3017
|
end
|
2911
3018
|
end
|
3019
|
+
|
2912
3020
|
describe 'api with a custom route setting' do
|
2913
|
-
before
|
3021
|
+
before do
|
2914
3022
|
subject.route_setting :custom, key: 'value'
|
2915
3023
|
subject.get 'one'
|
2916
3024
|
end
|
3025
|
+
|
2917
3026
|
it 'exposed' do
|
2918
3027
|
expect(subject.routes.count).to eq 1
|
2919
3028
|
route = subject.routes.first
|
2920
3029
|
expect(route.settings[:custom]).to eq(key: 'value')
|
2921
3030
|
end
|
2922
3031
|
end
|
3032
|
+
|
2923
3033
|
describe 'status' do
|
2924
3034
|
it 'can be set to arbitrary Integer value' do
|
2925
3035
|
subject.get '/foo' do
|
@@ -2928,6 +3038,7 @@ XML
|
|
2928
3038
|
get '/foo'
|
2929
3039
|
expect(last_response.status).to eq 210
|
2930
3040
|
end
|
3041
|
+
|
2931
3042
|
it 'can be set with a status code symbol' do
|
2932
3043
|
subject.get '/foo' do
|
2933
3044
|
status :see_other
|
@@ -2942,10 +3053,12 @@ XML
|
|
2942
3053
|
it 'empty array of routes' do
|
2943
3054
|
expect(subject.routes).to eq([])
|
2944
3055
|
end
|
3056
|
+
|
2945
3057
|
it 'empty array of routes' do
|
2946
3058
|
subject.desc 'grape api'
|
2947
3059
|
expect(subject.routes).to eq([])
|
2948
3060
|
end
|
3061
|
+
|
2949
3062
|
it 'describes a method' do
|
2950
3063
|
subject.desc 'first method'
|
2951
3064
|
subject.get :first
|
@@ -2956,6 +3069,7 @@ XML
|
|
2956
3069
|
expect(route.params).to eq({})
|
2957
3070
|
expect(route.options).to be_a_kind_of(Hash)
|
2958
3071
|
end
|
3072
|
+
|
2959
3073
|
it 'has params which does not include format and version as named captures' do
|
2960
3074
|
subject.version :v1, using: :path
|
2961
3075
|
subject.get :first
|
@@ -2963,6 +3077,7 @@ XML
|
|
2963
3077
|
expect(param_keys).not_to include('format')
|
2964
3078
|
expect(param_keys).not_to include('version')
|
2965
3079
|
end
|
3080
|
+
|
2966
3081
|
it 'describes methods separately' do
|
2967
3082
|
subject.desc 'first method'
|
2968
3083
|
subject.get :first
|
@@ -2976,6 +3091,7 @@ XML
|
|
2976
3091
|
{ description: 'second method', params: {} }
|
2977
3092
|
]
|
2978
3093
|
end
|
3094
|
+
|
2979
3095
|
it 'resets desc' do
|
2980
3096
|
subject.desc 'first method'
|
2981
3097
|
subject.get :first
|
@@ -2987,6 +3103,7 @@ XML
|
|
2987
3103
|
{ description: nil, params: {} }
|
2988
3104
|
]
|
2989
3105
|
end
|
3106
|
+
|
2990
3107
|
it 'namespaces and describe arbitrary parameters' do
|
2991
3108
|
subject.namespace 'ns' do
|
2992
3109
|
desc 'ns second', foo: 'bar'
|
@@ -2998,6 +3115,7 @@ XML
|
|
2998
3115
|
{ description: 'ns second', foo: 'bar', params: {} }
|
2999
3116
|
]
|
3000
3117
|
end
|
3118
|
+
|
3001
3119
|
it 'includes details' do
|
3002
3120
|
subject.desc 'method', details: 'method details'
|
3003
3121
|
subject.get 'method'
|
@@ -3007,6 +3125,7 @@ XML
|
|
3007
3125
|
{ description: 'method', details: 'method details', params: {} }
|
3008
3126
|
]
|
3009
3127
|
end
|
3128
|
+
|
3010
3129
|
it 'describes a method with parameters' do
|
3011
3130
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
3012
3131
|
subject.get 'reverse' do
|
@@ -3018,6 +3137,7 @@ XML
|
|
3018
3137
|
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
|
3019
3138
|
]
|
3020
3139
|
end
|
3140
|
+
|
3021
3141
|
it 'does not inherit param descriptions in consequent namespaces' do
|
3022
3142
|
subject.desc 'global description'
|
3023
3143
|
subject.params do
|
@@ -3048,6 +3168,7 @@ XML
|
|
3048
3168
|
} }
|
3049
3169
|
]
|
3050
3170
|
end
|
3171
|
+
|
3051
3172
|
it 'merges the parameters of the namespace with the parameters of the method' do
|
3052
3173
|
subject.desc 'namespace'
|
3053
3174
|
subject.params do
|
@@ -3072,6 +3193,7 @@ XML
|
|
3072
3193
|
} }
|
3073
3194
|
]
|
3074
3195
|
end
|
3196
|
+
|
3075
3197
|
it 'merges the parameters of nested namespaces' do
|
3076
3198
|
subject.desc 'ns1'
|
3077
3199
|
subject.params do
|
@@ -3104,6 +3226,7 @@ XML
|
|
3104
3226
|
} }
|
3105
3227
|
]
|
3106
3228
|
end
|
3229
|
+
|
3107
3230
|
it 'groups nested params and prevents overwriting of params with same name in different groups' do
|
3108
3231
|
subject.desc 'method'
|
3109
3232
|
subject.params do
|
@@ -3119,14 +3242,15 @@ XML
|
|
3119
3242
|
subject.get 'method'
|
3120
3243
|
|
3121
3244
|
expect(subject.routes.map(&:params)).to eq [{
|
3122
|
-
'group1'
|
3245
|
+
'group1' => { required: true, type: 'Array' },
|
3123
3246
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3124
3247
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3125
|
-
'group2'
|
3248
|
+
'group2' => { required: true, type: 'Array' },
|
3126
3249
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3127
3250
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3128
3251
|
}]
|
3129
3252
|
end
|
3253
|
+
|
3130
3254
|
it 'uses full name of parameters in nested groups' do
|
3131
3255
|
subject.desc 'nesting'
|
3132
3256
|
subject.params do
|
@@ -3147,6 +3271,7 @@ XML
|
|
3147
3271
|
} }
|
3148
3272
|
]
|
3149
3273
|
end
|
3274
|
+
|
3150
3275
|
it 'allows to set the type attribute on :group element' do
|
3151
3276
|
subject.params do
|
3152
3277
|
group :foo, type: Array do
|
@@ -3154,6 +3279,7 @@ XML
|
|
3154
3279
|
end
|
3155
3280
|
end
|
3156
3281
|
end
|
3282
|
+
|
3157
3283
|
it 'parses parameters when no description is given' do
|
3158
3284
|
subject.params do
|
3159
3285
|
requires :one_param, desc: 'one param'
|
@@ -3165,6 +3291,7 @@ XML
|
|
3165
3291
|
{ description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
|
3166
3292
|
]
|
3167
3293
|
end
|
3294
|
+
|
3168
3295
|
it 'does not symbolize params' do
|
3169
3296
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
3170
3297
|
subject.get 'reverse/:s' do
|
@@ -3223,7 +3350,7 @@ XML
|
|
3223
3350
|
subject.version 'v1', using: :path
|
3224
3351
|
|
3225
3352
|
subject.namespace :cool do
|
3226
|
-
app = Class.new(Grape::API)
|
3353
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3227
3354
|
app.get('/awesome') do
|
3228
3355
|
'yo'
|
3229
3356
|
end
|
@@ -3239,12 +3366,12 @@ XML
|
|
3239
3366
|
subject.version 'v1', using: :path
|
3240
3367
|
|
3241
3368
|
subject.namespace :cool do
|
3242
|
-
inner_app = Class.new(Grape::API)
|
3369
|
+
inner_app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3243
3370
|
inner_app.get('/awesome') do
|
3244
3371
|
'yo'
|
3245
3372
|
end
|
3246
3373
|
|
3247
|
-
app = Class.new(Grape::API)
|
3374
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3248
3375
|
app.mount inner_app
|
3249
3376
|
mount app
|
3250
3377
|
end
|
@@ -3259,7 +3386,7 @@ XML
|
|
3259
3386
|
rack_response("rescued from #{e.message}", 202)
|
3260
3387
|
end
|
3261
3388
|
|
3262
|
-
app = Class.new(
|
3389
|
+
app = Class.new(described_class)
|
3263
3390
|
|
3264
3391
|
subject.namespace :mounted do
|
3265
3392
|
app.rescue_from ArgumentError
|
@@ -3268,15 +3395,16 @@ XML
|
|
3268
3395
|
end
|
3269
3396
|
|
3270
3397
|
get '/mounted/fail'
|
3271
|
-
expect(last_response.status).to
|
3398
|
+
expect(last_response.status).to be 202
|
3272
3399
|
expect(last_response.body).to eq('rescued from doh!')
|
3273
3400
|
end
|
3401
|
+
|
3274
3402
|
it 'prefers rescues defined by mounted if they rescue similar error class' do
|
3275
3403
|
subject.rescue_from StandardError do
|
3276
3404
|
rack_response('outer rescue')
|
3277
3405
|
end
|
3278
3406
|
|
3279
|
-
app = Class.new(
|
3407
|
+
app = Class.new(described_class)
|
3280
3408
|
|
3281
3409
|
subject.namespace :mounted do
|
3282
3410
|
rescue_from StandardError do
|
@@ -3289,12 +3417,13 @@ XML
|
|
3289
3417
|
get '/mounted/fail'
|
3290
3418
|
expect(last_response.body).to eq('inner rescue')
|
3291
3419
|
end
|
3420
|
+
|
3292
3421
|
it 'prefers rescues defined by mounted even if outer is more specific' do
|
3293
3422
|
subject.rescue_from ArgumentError do
|
3294
3423
|
rack_response('outer rescue')
|
3295
3424
|
end
|
3296
3425
|
|
3297
|
-
app = Class.new(
|
3426
|
+
app = Class.new(described_class)
|
3298
3427
|
|
3299
3428
|
subject.namespace :mounted do
|
3300
3429
|
rescue_from StandardError do
|
@@ -3307,12 +3436,13 @@ XML
|
|
3307
3436
|
get '/mounted/fail'
|
3308
3437
|
expect(last_response.body).to eq('inner rescue')
|
3309
3438
|
end
|
3439
|
+
|
3310
3440
|
it 'prefers more specific rescues defined by mounted' do
|
3311
3441
|
subject.rescue_from StandardError do
|
3312
3442
|
rack_response('outer rescue')
|
3313
3443
|
end
|
3314
3444
|
|
3315
|
-
app = Class.new(
|
3445
|
+
app = Class.new(described_class)
|
3316
3446
|
|
3317
3447
|
subject.namespace :mounted do
|
3318
3448
|
rescue_from ArgumentError do
|
@@ -3329,19 +3459,19 @@ XML
|
|
3329
3459
|
|
3330
3460
|
it 'collects the routes of the mounted api' do
|
3331
3461
|
subject.namespace :cool do
|
3332
|
-
app = Class.new(Grape::API)
|
3462
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3333
3463
|
app.get('/awesome') {}
|
3334
3464
|
app.post('/sauce') {}
|
3335
3465
|
mount app
|
3336
3466
|
end
|
3337
3467
|
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{
|
3468
|
+
expect(subject.routes.first.path).to match(%r{/cool/awesome})
|
3469
|
+
expect(subject.routes.last.path).to match(%r{/cool/sauce})
|
3340
3470
|
end
|
3341
3471
|
|
3342
3472
|
it 'mounts on a path' do
|
3343
3473
|
subject.namespace :cool do
|
3344
|
-
app = Class.new(Grape::API)
|
3474
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3345
3475
|
app.get '/awesome' do
|
3346
3476
|
'sauce'
|
3347
3477
|
end
|
@@ -3353,12 +3483,12 @@ XML
|
|
3353
3483
|
end
|
3354
3484
|
|
3355
3485
|
it 'mounts on a nested path' do
|
3356
|
-
APP1 = Class.new(
|
3357
|
-
APP2 = Class.new(
|
3486
|
+
APP1 = Class.new(described_class)
|
3487
|
+
APP2 = Class.new(described_class)
|
3358
3488
|
APP2.get '/nice' do
|
3359
3489
|
'play'
|
3360
3490
|
end
|
3361
|
-
#
|
3491
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3362
3492
|
APP3 = subject
|
3363
3493
|
APP3.mount APP1 => '/app1'
|
3364
3494
|
APP1.mount APP2 => '/app2'
|
@@ -3370,7 +3500,7 @@ XML
|
|
3370
3500
|
end
|
3371
3501
|
|
3372
3502
|
it 'responds to options' do
|
3373
|
-
app = Class.new(
|
3503
|
+
app = Class.new(described_class)
|
3374
3504
|
app.get '/colour' do
|
3375
3505
|
'red'
|
3376
3506
|
end
|
@@ -3384,21 +3514,21 @@ XML
|
|
3384
3514
|
end
|
3385
3515
|
|
3386
3516
|
get '/apples/colour'
|
3387
|
-
expect(last_response.status).to
|
3517
|
+
expect(last_response.status).to be 200
|
3388
3518
|
expect(last_response.body).to eq('red')
|
3389
3519
|
options '/apples/colour'
|
3390
|
-
expect(last_response.status).to
|
3520
|
+
expect(last_response.status).to be 204
|
3391
3521
|
get '/apples/pears/colour'
|
3392
|
-
expect(last_response.status).to
|
3522
|
+
expect(last_response.status).to be 200
|
3393
3523
|
expect(last_response.body).to eq('green')
|
3394
3524
|
options '/apples/pears/colour'
|
3395
|
-
expect(last_response.status).to
|
3525
|
+
expect(last_response.status).to be 204
|
3396
3526
|
end
|
3397
3527
|
|
3398
3528
|
it 'responds to options with path versioning' do
|
3399
3529
|
subject.version 'v1', using: :path
|
3400
3530
|
subject.namespace :apples do
|
3401
|
-
app = Class.new(Grape::API)
|
3531
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3402
3532
|
app.get('/colour') do
|
3403
3533
|
'red'
|
3404
3534
|
end
|
@@ -3406,14 +3536,14 @@ XML
|
|
3406
3536
|
end
|
3407
3537
|
|
3408
3538
|
get '/v1/apples/colour'
|
3409
|
-
expect(last_response.status).to
|
3539
|
+
expect(last_response.status).to be 200
|
3410
3540
|
expect(last_response.body).to eq('red')
|
3411
3541
|
options '/v1/apples/colour'
|
3412
|
-
expect(last_response.status).to
|
3542
|
+
expect(last_response.status).to be 204
|
3413
3543
|
end
|
3414
3544
|
|
3415
3545
|
it 'mounts a versioned API with nested resources' do
|
3416
|
-
api = Class.new(
|
3546
|
+
api = Class.new(described_class) do
|
3417
3547
|
version 'v1'
|
3418
3548
|
resources :users do
|
3419
3549
|
get :hello do
|
@@ -3428,7 +3558,7 @@ XML
|
|
3428
3558
|
end
|
3429
3559
|
|
3430
3560
|
it 'mounts a prefixed API with nested resources' do
|
3431
|
-
api = Class.new(
|
3561
|
+
api = Class.new(described_class) do
|
3432
3562
|
prefix 'api'
|
3433
3563
|
resources :users do
|
3434
3564
|
get :hello do
|
@@ -3443,7 +3573,7 @@ XML
|
|
3443
3573
|
end
|
3444
3574
|
|
3445
3575
|
it 'applies format to a mounted API with nested resources' do
|
3446
|
-
api = Class.new(
|
3576
|
+
api = Class.new(described_class) do
|
3447
3577
|
format :json
|
3448
3578
|
resources :users do
|
3449
3579
|
get do
|
@@ -3458,7 +3588,7 @@ XML
|
|
3458
3588
|
end
|
3459
3589
|
|
3460
3590
|
it 'applies auth to a mounted API with nested resources' do
|
3461
|
-
api = Class.new(
|
3591
|
+
api = Class.new(described_class) do
|
3462
3592
|
format :json
|
3463
3593
|
http_basic do |username, password|
|
3464
3594
|
username == 'username' && password == 'password'
|
@@ -3479,7 +3609,7 @@ XML
|
|
3479
3609
|
end
|
3480
3610
|
|
3481
3611
|
it 'mounts multiple versioned APIs with nested resources' do
|
3482
|
-
api1 = Class.new(
|
3612
|
+
api1 = Class.new(described_class) do
|
3483
3613
|
version 'one', using: :header, vendor: 'test'
|
3484
3614
|
resources :users do
|
3485
3615
|
get :hello do
|
@@ -3488,7 +3618,7 @@ XML
|
|
3488
3618
|
end
|
3489
3619
|
end
|
3490
3620
|
|
3491
|
-
api2 = Class.new(
|
3621
|
+
api2 = Class.new(described_class) do
|
3492
3622
|
version 'two', using: :header, vendor: 'test'
|
3493
3623
|
resources :users do
|
3494
3624
|
get :hello do
|
@@ -3507,7 +3637,7 @@ XML
|
|
3507
3637
|
end
|
3508
3638
|
|
3509
3639
|
it 'recognizes potential versions with mounted path' do
|
3510
|
-
a = Class.new(
|
3640
|
+
a = Class.new(described_class) do
|
3511
3641
|
version :v1, using: :path
|
3512
3642
|
|
3513
3643
|
get '/hello' do
|
@@ -3515,7 +3645,7 @@ XML
|
|
3515
3645
|
end
|
3516
3646
|
end
|
3517
3647
|
|
3518
|
-
b = Class.new(
|
3648
|
+
b = Class.new(described_class) do
|
3519
3649
|
version :v1, using: :path
|
3520
3650
|
|
3521
3651
|
get '/world' do
|
@@ -3535,11 +3665,11 @@ XML
|
|
3535
3665
|
|
3536
3666
|
context 'when mounting class extends a subclass of Grape::API' do
|
3537
3667
|
it 'mounts APIs with the same superclass' do
|
3538
|
-
base_api = Class.new(
|
3668
|
+
base_api = Class.new(described_class)
|
3539
3669
|
a = Class.new(base_api)
|
3540
3670
|
b = Class.new(base_api)
|
3541
3671
|
|
3542
|
-
expect { a.mount b }.
|
3672
|
+
expect { a.mount b }.not_to raise_error
|
3543
3673
|
end
|
3544
3674
|
end
|
3545
3675
|
|
@@ -3549,6 +3679,7 @@ XML
|
|
3549
3679
|
def self.included(base)
|
3550
3680
|
base.extend(ClassMethods)
|
3551
3681
|
end
|
3682
|
+
|
3552
3683
|
module ClassMethods
|
3553
3684
|
def my_method
|
3554
3685
|
@test = true
|
@@ -3557,22 +3688,22 @@ XML
|
|
3557
3688
|
end
|
3558
3689
|
end
|
3559
3690
|
|
3560
|
-
it '
|
3691
|
+
it 'correctlies include module in nested mount' do
|
3561
3692
|
module_to_include = included_module
|
3562
|
-
v1 = Class.new(
|
3693
|
+
v1 = Class.new(described_class) do
|
3563
3694
|
version :v1, using: :path
|
3564
3695
|
include module_to_include
|
3565
3696
|
my_method
|
3566
3697
|
end
|
3567
|
-
v2 = Class.new(
|
3698
|
+
v2 = Class.new(described_class) do
|
3568
3699
|
version :v2, using: :path
|
3569
3700
|
end
|
3570
|
-
segment_base = Class.new(
|
3701
|
+
segment_base = Class.new(described_class) do
|
3571
3702
|
mount v1
|
3572
3703
|
mount v2
|
3573
3704
|
end
|
3574
3705
|
|
3575
|
-
Class.new(
|
3706
|
+
Class.new(described_class) do
|
3576
3707
|
mount segment_base
|
3577
3708
|
end
|
3578
3709
|
|
@@ -3607,7 +3738,7 @@ XML
|
|
3607
3738
|
end
|
3608
3739
|
|
3609
3740
|
describe '.endpoint' do
|
3610
|
-
before
|
3741
|
+
before do
|
3611
3742
|
subject.format :json
|
3612
3743
|
subject.get '/endpoint/options' do
|
3613
3744
|
{
|
@@ -3616,6 +3747,7 @@ XML
|
|
3616
3747
|
}
|
3617
3748
|
end
|
3618
3749
|
end
|
3750
|
+
|
3619
3751
|
it 'path' do
|
3620
3752
|
get '/endpoint/options'
|
3621
3753
|
options = ::Grape::Json.load(last_response.body)
|
@@ -3627,7 +3759,7 @@ XML
|
|
3627
3759
|
|
3628
3760
|
describe '.route' do
|
3629
3761
|
context 'plain' do
|
3630
|
-
before
|
3762
|
+
before do
|
3631
3763
|
subject.get '/' do
|
3632
3764
|
route.path
|
3633
3765
|
end
|
@@ -3635,6 +3767,7 @@ XML
|
|
3635
3767
|
route.path
|
3636
3768
|
end
|
3637
3769
|
end
|
3770
|
+
|
3638
3771
|
it 'provides access to route info' do
|
3639
3772
|
get '/'
|
3640
3773
|
expect(last_response.body).to eq('/(.:format)')
|
@@ -3642,8 +3775,9 @@ XML
|
|
3642
3775
|
expect(last_response.body).to eq('/path(.:format)')
|
3643
3776
|
end
|
3644
3777
|
end
|
3778
|
+
|
3645
3779
|
context 'with desc' do
|
3646
|
-
before
|
3780
|
+
before do
|
3647
3781
|
subject.desc 'returns description'
|
3648
3782
|
subject.get '/description' do
|
3649
3783
|
route.description
|
@@ -3653,82 +3787,98 @@ XML
|
|
3653
3787
|
route.params[params[:id]]
|
3654
3788
|
end
|
3655
3789
|
end
|
3790
|
+
|
3656
3791
|
it 'returns route description' do
|
3657
3792
|
get '/description'
|
3658
3793
|
expect(last_response.body).to eq('returns description')
|
3659
3794
|
end
|
3795
|
+
|
3660
3796
|
it 'returns route parameters' do
|
3661
3797
|
get '/params/x'
|
3662
3798
|
expect(last_response.body).to eq('y')
|
3663
3799
|
end
|
3664
3800
|
end
|
3665
3801
|
end
|
3802
|
+
|
3666
3803
|
describe '.format' do
|
3667
3804
|
context ':txt' do
|
3668
|
-
before
|
3805
|
+
before do
|
3669
3806
|
subject.format :txt
|
3670
3807
|
subject.content_type :json, 'application/json'
|
3671
3808
|
subject.get '/meaning_of_life' do
|
3672
3809
|
{ meaning_of_life: 42 }
|
3673
3810
|
end
|
3674
3811
|
end
|
3812
|
+
|
3675
3813
|
it 'forces txt without an extension' do
|
3676
3814
|
get '/meaning_of_life'
|
3677
3815
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3678
3816
|
end
|
3817
|
+
|
3679
3818
|
it 'does not force txt with an extension' do
|
3680
3819
|
get '/meaning_of_life.json'
|
3681
3820
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3682
3821
|
end
|
3822
|
+
|
3683
3823
|
it 'forces txt from a non-accepting header' do
|
3684
3824
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3685
3825
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3686
3826
|
end
|
3687
3827
|
end
|
3828
|
+
|
3688
3829
|
context ':txt only' do
|
3689
|
-
before
|
3830
|
+
before do
|
3690
3831
|
subject.format :txt
|
3691
3832
|
subject.get '/meaning_of_life' do
|
3692
3833
|
{ meaning_of_life: 42 }
|
3693
3834
|
end
|
3694
3835
|
end
|
3836
|
+
|
3695
3837
|
it 'forces txt without an extension' do
|
3696
3838
|
get '/meaning_of_life'
|
3697
3839
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3698
3840
|
end
|
3841
|
+
|
3699
3842
|
it 'accepts specified extension' do
|
3700
3843
|
get '/meaning_of_life.txt'
|
3701
3844
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3702
3845
|
end
|
3846
|
+
|
3703
3847
|
it 'does not accept extensions other than specified' do
|
3704
3848
|
get '/meaning_of_life.json'
|
3705
3849
|
expect(last_response.status).to eq(404)
|
3706
3850
|
end
|
3851
|
+
|
3707
3852
|
it 'forces txt from a non-accepting header' do
|
3708
3853
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3709
3854
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3710
3855
|
end
|
3711
3856
|
end
|
3857
|
+
|
3712
3858
|
context ':json' do
|
3713
|
-
before
|
3859
|
+
before do
|
3714
3860
|
subject.format :json
|
3715
3861
|
subject.content_type :txt, 'text/plain'
|
3716
3862
|
subject.get '/meaning_of_life' do
|
3717
3863
|
{ meaning_of_life: 42 }
|
3718
3864
|
end
|
3719
3865
|
end
|
3866
|
+
|
3720
3867
|
it 'forces json without an extension' do
|
3721
3868
|
get '/meaning_of_life'
|
3722
3869
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3723
3870
|
end
|
3871
|
+
|
3724
3872
|
it 'does not force json with an extension' do
|
3725
3873
|
get '/meaning_of_life.txt'
|
3726
3874
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3727
3875
|
end
|
3876
|
+
|
3728
3877
|
it 'forces json from a non-accepting header' do
|
3729
3878
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
|
3730
3879
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3731
3880
|
end
|
3881
|
+
|
3732
3882
|
it 'can be overwritten with an explicit content type' do
|
3733
3883
|
subject.get '/meaning_of_life_with_content_type' do
|
3734
3884
|
content_type 'text/plain'
|
@@ -3737,6 +3887,7 @@ XML
|
|
3737
3887
|
get '/meaning_of_life_with_content_type'
|
3738
3888
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3739
3889
|
end
|
3890
|
+
|
3740
3891
|
it 'raised :error from middleware' do
|
3741
3892
|
middleware = Class.new(Grape::Middleware::Base) do
|
3742
3893
|
def before
|
@@ -3751,6 +3902,7 @@ XML
|
|
3751
3902
|
expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
|
3752
3903
|
end
|
3753
3904
|
end
|
3905
|
+
|
3754
3906
|
context ':serializable_hash' do
|
3755
3907
|
class SerializableHashExample
|
3756
3908
|
def serializable_hash
|
@@ -3758,9 +3910,10 @@ XML
|
|
3758
3910
|
end
|
3759
3911
|
end
|
3760
3912
|
|
3761
|
-
before
|
3913
|
+
before do
|
3762
3914
|
subject.format :serializable_hash
|
3763
3915
|
end
|
3916
|
+
|
3764
3917
|
it 'instance' do
|
3765
3918
|
subject.get '/example' do
|
3766
3919
|
SerializableHashExample.new
|
@@ -3768,6 +3921,7 @@ XML
|
|
3768
3921
|
get '/example'
|
3769
3922
|
expect(last_response.body).to eq('{"abc":"def"}')
|
3770
3923
|
end
|
3924
|
+
|
3771
3925
|
it 'root' do
|
3772
3926
|
subject.get '/example' do
|
3773
3927
|
{ 'root' => SerializableHashExample.new }
|
@@ -3775,6 +3929,7 @@ XML
|
|
3775
3929
|
get '/example'
|
3776
3930
|
expect(last_response.body).to eq('{"root":{"abc":"def"}}')
|
3777
3931
|
end
|
3932
|
+
|
3778
3933
|
it 'array' do
|
3779
3934
|
subject.get '/examples' do
|
3780
3935
|
[SerializableHashExample.new, SerializableHashExample.new]
|
@@ -3783,23 +3938,26 @@ XML
|
|
3783
3938
|
expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
|
3784
3939
|
end
|
3785
3940
|
end
|
3941
|
+
|
3786
3942
|
context ':xml' do
|
3787
|
-
before
|
3943
|
+
before do
|
3788
3944
|
subject.format :xml
|
3789
3945
|
end
|
3946
|
+
|
3790
3947
|
it 'string' do
|
3791
3948
|
subject.get '/example' do
|
3792
3949
|
'example'
|
3793
3950
|
end
|
3794
3951
|
get '/example'
|
3795
3952
|
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
|
3953
|
+
expect(last_response.body).to eq <<~XML
|
3954
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3955
|
+
<error>
|
3956
|
+
<message>cannot convert String to xml</message>
|
3957
|
+
</error>
|
3958
|
+
XML
|
3802
3959
|
end
|
3960
|
+
|
3803
3961
|
it 'hash' do
|
3804
3962
|
subject.get '/example' do
|
3805
3963
|
{
|
@@ -3809,28 +3967,30 @@ XML
|
|
3809
3967
|
end
|
3810
3968
|
get '/example'
|
3811
3969
|
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
|
3970
|
+
expect(last_response.body).to eq <<~XML
|
3971
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3972
|
+
<hash>
|
3973
|
+
<example1>example1</example1>
|
3974
|
+
<example2>example2</example2>
|
3975
|
+
</hash>
|
3976
|
+
XML
|
3819
3977
|
end
|
3978
|
+
|
3820
3979
|
it 'array' do
|
3821
3980
|
subject.get '/example' do
|
3822
3981
|
%w[example1 example2]
|
3823
3982
|
end
|
3824
3983
|
get '/example'
|
3825
3984
|
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
|
3985
|
+
expect(last_response.body).to eq <<~XML
|
3986
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
3987
|
+
<strings type="array">
|
3988
|
+
<string>example1</string>
|
3989
|
+
<string>example2</string>
|
3990
|
+
</strings>
|
3991
|
+
XML
|
3833
3992
|
end
|
3993
|
+
|
3834
3994
|
it 'raised :error from middleware' do
|
3835
3995
|
middleware = Class.new(Grape::Middleware::Base) do
|
3836
3996
|
def before
|
@@ -3842,12 +4002,12 @@ XML
|
|
3842
4002
|
end
|
3843
4003
|
get '/'
|
3844
4004
|
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
|
4005
|
+
expect(last_response.body).to eq <<~XML
|
4006
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
4007
|
+
<error>
|
4008
|
+
<message>Unauthorized</message>
|
4009
|
+
</error>
|
4010
|
+
XML
|
3851
4011
|
end
|
3852
4012
|
end
|
3853
4013
|
end
|
@@ -3892,12 +4052,12 @@ XML
|
|
3892
4052
|
|
3893
4053
|
context 'catch-all' do
|
3894
4054
|
before do
|
3895
|
-
api1 = Class.new(
|
4055
|
+
api1 = Class.new(described_class)
|
3896
4056
|
api1.version 'v1', using: :path
|
3897
4057
|
api1.get 'hello' do
|
3898
4058
|
'v1'
|
3899
4059
|
end
|
3900
|
-
api2 = Class.new(
|
4060
|
+
api2 = Class.new(described_class)
|
3901
4061
|
api2.version 'v2', using: :path
|
3902
4062
|
api2.get 'hello' do
|
3903
4063
|
'v2'
|
@@ -3905,6 +4065,7 @@ XML
|
|
3905
4065
|
subject.mount api1
|
3906
4066
|
subject.mount api2
|
3907
4067
|
end
|
4068
|
+
|
3908
4069
|
[true, false].each do |anchor|
|
3909
4070
|
it "anchor=#{anchor}" do
|
3910
4071
|
subject.route :any, '*path', anchor: anchor do
|
@@ -3937,6 +4098,7 @@ XML
|
|
3937
4098
|
expect(last_response.status).to eq(404)
|
3938
4099
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3939
4100
|
end
|
4101
|
+
|
3940
4102
|
it 'does not cascade' do
|
3941
4103
|
subject.version 'v2', using: :path, cascade: false
|
3942
4104
|
get '/v2/hello'
|
@@ -3944,6 +4106,7 @@ XML
|
|
3944
4106
|
expect(last_response.headers.keys).not_to include 'X-Cascade'
|
3945
4107
|
end
|
3946
4108
|
end
|
4109
|
+
|
3947
4110
|
context 'via endpoint' do
|
3948
4111
|
it 'cascades' do
|
3949
4112
|
subject.cascade true
|
@@ -3951,6 +4114,7 @@ XML
|
|
3951
4114
|
expect(last_response.status).to eq(404)
|
3952
4115
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3953
4116
|
end
|
4117
|
+
|
3954
4118
|
it 'does not cascade' do
|
3955
4119
|
subject.cascade false
|
3956
4120
|
get '/hello'
|
@@ -3993,6 +4157,20 @@ XML
|
|
3993
4157
|
end
|
3994
4158
|
end
|
3995
4159
|
|
4160
|
+
context 'with non-UTF-8 characters in specified format' do
|
4161
|
+
it 'converts the characters' do
|
4162
|
+
subject.format :json
|
4163
|
+
subject.content_type :json, 'application/json'
|
4164
|
+
subject.get '/something' do
|
4165
|
+
'foo'
|
4166
|
+
end
|
4167
|
+
get '/something?format=%0A%0B%BF'
|
4168
|
+
expect(last_response.status).to eq(406)
|
4169
|
+
message = "The requested format '\n\u000b\357\277\275' is not supported."
|
4170
|
+
expect(last_response.body).to eq({ error: message }.to_json)
|
4171
|
+
end
|
4172
|
+
end
|
4173
|
+
|
3996
4174
|
context 'body' do
|
3997
4175
|
context 'false' do
|
3998
4176
|
before do
|
@@ -4000,12 +4178,14 @@ XML
|
|
4000
4178
|
body false
|
4001
4179
|
end
|
4002
4180
|
end
|
4181
|
+
|
4003
4182
|
it 'returns blank body' do
|
4004
4183
|
get '/blank'
|
4005
4184
|
expect(last_response.status).to eq(204)
|
4006
4185
|
expect(last_response.body).to be_blank
|
4007
4186
|
end
|
4008
4187
|
end
|
4188
|
+
|
4009
4189
|
context 'plain text' do
|
4010
4190
|
before do
|
4011
4191
|
subject.get '/text' do
|
@@ -4014,6 +4194,7 @@ XML
|
|
4014
4194
|
'ignored'
|
4015
4195
|
end
|
4016
4196
|
end
|
4197
|
+
|
4017
4198
|
it 'returns blank body' do
|
4018
4199
|
get '/text'
|
4019
4200
|
expect(last_response.status).to eq(200)
|
@@ -4023,7 +4204,7 @@ XML
|
|
4023
4204
|
end
|
4024
4205
|
|
4025
4206
|
describe 'normal class methods' do
|
4026
|
-
subject(:grape_api) { Class.new(
|
4207
|
+
subject(:grape_api) { Class.new(described_class) }
|
4027
4208
|
|
4028
4209
|
before do
|
4029
4210
|
stub_const('MyAPI', grape_api)
|
@@ -4040,10 +4221,54 @@ XML
|
|
4040
4221
|
end
|
4041
4222
|
end
|
4042
4223
|
|
4224
|
+
describe '.inherited' do
|
4225
|
+
context 'overriding within class' do
|
4226
|
+
let(:root_api) do
|
4227
|
+
Class.new(described_class) do
|
4228
|
+
@bar = 'Hello, world'
|
4229
|
+
|
4230
|
+
def self.inherited(child_api)
|
4231
|
+
super
|
4232
|
+
child_api.instance_variable_set(:@foo, @bar.dup)
|
4233
|
+
end
|
4234
|
+
end
|
4235
|
+
end
|
4236
|
+
|
4237
|
+
let(:child_api) { Class.new(root_api) }
|
4238
|
+
|
4239
|
+
it 'allows overriding the hook' do
|
4240
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4241
|
+
end
|
4242
|
+
end
|
4243
|
+
|
4244
|
+
context 'overriding via composition' do
|
4245
|
+
module Inherited
|
4246
|
+
def inherited(api)
|
4247
|
+
super
|
4248
|
+
api.instance_variable_set(:@foo, @bar.dup)
|
4249
|
+
end
|
4250
|
+
end
|
4251
|
+
|
4252
|
+
let(:root_api) do
|
4253
|
+
Class.new(described_class) do
|
4254
|
+
@bar = 'Hello, world'
|
4255
|
+
extend Inherited
|
4256
|
+
end
|
4257
|
+
end
|
4258
|
+
|
4259
|
+
let(:child_api) { Class.new(root_api) }
|
4260
|
+
|
4261
|
+
it 'allows overriding the hook' do
|
4262
|
+
expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
|
4263
|
+
end
|
4264
|
+
end
|
4265
|
+
end
|
4266
|
+
|
4043
4267
|
describe 'const_missing' do
|
4044
|
-
subject(:grape_api) { Class.new(
|
4268
|
+
subject(:grape_api) { Class.new(described_class) }
|
4269
|
+
|
4045
4270
|
let(:mounted) do
|
4046
|
-
Class.new(
|
4271
|
+
Class.new(described_class) do
|
4047
4272
|
get '/missing' do
|
4048
4273
|
SomeRandomConstant
|
4049
4274
|
end
|
@@ -4058,6 +4283,12 @@ XML
|
|
4058
4283
|
end
|
4059
4284
|
|
4060
4285
|
describe 'custom route helpers on nested APIs' do
|
4286
|
+
subject(:grape_api) do
|
4287
|
+
Class.new(described_class) do
|
4288
|
+
version 'v1', using: :path
|
4289
|
+
end
|
4290
|
+
end
|
4291
|
+
|
4061
4292
|
let(:shared_api_module) do
|
4062
4293
|
Module.new do
|
4063
4294
|
# rubocop:disable Style/ExplicitBlockArgument because this causes
|
@@ -4091,7 +4322,7 @@ XML
|
|
4091
4322
|
let(:orders_root) do
|
4092
4323
|
shared = shared_api_definitions
|
4093
4324
|
find = orders_find_endpoint
|
4094
|
-
Class.new(
|
4325
|
+
Class.new(described_class) do
|
4095
4326
|
include shared
|
4096
4327
|
|
4097
4328
|
namespace(:orders) do
|
@@ -4101,7 +4332,7 @@ XML
|
|
4101
4332
|
end
|
4102
4333
|
let(:orders_find_endpoint) do
|
4103
4334
|
shared = shared_api_definitions
|
4104
|
-
Class.new(
|
4335
|
+
Class.new(described_class) do
|
4105
4336
|
include shared
|
4106
4337
|
|
4107
4338
|
uniqe_id_route do
|
@@ -4112,11 +4343,6 @@ XML
|
|
4112
4343
|
end
|
4113
4344
|
end
|
4114
4345
|
end
|
4115
|
-
subject(:grape_api) do
|
4116
|
-
Class.new(Grape::API) do
|
4117
|
-
version 'v1', using: :path
|
4118
|
-
end
|
4119
|
-
end
|
4120
4346
|
|
4121
4347
|
before do
|
4122
4348
|
Grape::API::Instance.extend(shared_api_module)
|