grape 1.5.2 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|