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