grape 1.3.3 → 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 +111 -2
- data/CONTRIBUTING.md +2 -1
- data/README.md +135 -23
- data/UPGRADING.md +237 -46
- data/grape.gemspec +5 -5
- data/lib/grape/api/instance.rb +34 -42
- data/lib/grape/api.rb +21 -16
- data/lib/grape/cookies.rb +2 -0
- data/lib/grape/dsl/callbacks.rb +1 -1
- data/lib/grape/dsl/desc.rb +3 -5
- data/lib/grape/dsl/headers.rb +5 -2
- data/lib/grape/dsl/helpers.rb +8 -5
- data/lib/grape/dsl/inside_route.rb +72 -53
- data/lib/grape/dsl/middleware.rb +4 -4
- data/lib/grape/dsl/parameters.rb +11 -7
- data/lib/grape/dsl/request_response.rb +9 -6
- data/lib/grape/dsl/routing.rb +8 -9
- data/lib/grape/dsl/settings.rb +5 -5
- data/lib/grape/dsl/validations.rb +18 -1
- data/lib/grape/eager_load.rb +1 -1
- data/lib/grape/endpoint.rb +29 -42
- 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 +2 -3
- data/lib/grape/exceptions/validation_errors.rb +1 -1
- 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/base.rb +3 -3
- data/lib/grape/middleware/auth/dsl.rb +7 -1
- data/lib/grape/middleware/base.rb +6 -3
- data/lib/grape/middleware/error.rb +11 -13
- data/lib/grape/middleware/formatter.rb +7 -7
- data/lib/grape/middleware/stack.rb +10 -3
- data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
- data/lib/grape/middleware/versioner/header.rb +6 -4
- data/lib/grape/middleware/versioner/param.rb +1 -0
- data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
- data/lib/grape/middleware/versioner/path.rb +2 -0
- data/lib/grape/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 +4 -1
- data/lib/grape/router/attribute_translator.rb +3 -3
- data/lib/grape/router/pattern.rb +1 -1
- data/lib/grape/router/route.rb +2 -2
- data/lib/grape/router.rb +31 -30
- data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
- data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
- data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
- data/lib/grape/util/base_inheritable.rb +2 -2
- data/lib/grape/util/inheritable_setting.rb +1 -3
- data/lib/grape/util/lazy_value.rb +4 -2
- data/lib/grape/util/strict_hash_configuration.rb +1 -1
- data/lib/grape/validations/attributes_iterator.rb +8 -0
- data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
- data/lib/grape/validations/params_scope.rb +97 -62
- data/lib/grape/validations/single_attribute_iterator.rb +1 -1
- data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
- data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
- data/lib/grape/validations/types/invalid_value.rb +24 -0
- data/lib/grape/validations/types/json.rb +2 -1
- data/lib/grape/validations/types/primitive_coercer.rb +4 -5
- data/lib/grape/validations/types.rb +1 -4
- data/lib/grape/validations/validator_factory.rb +1 -1
- 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 +74 -69
- data/lib/grape/validations/validators/coerce.rb +63 -76
- data/lib/grape/validations/validators/default.rb +36 -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 -19
- 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 +7 -3
- 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 +25 -19
- data/spec/grape/api_spec.rb +576 -211
- 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 +185 -34
- 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 +848 -0
- data/spec/grape/endpoint_spec.rb +77 -589
- data/spec/grape/entity_spec.rb +29 -23
- 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 +13 -9
- 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 +61 -21
- data/spec/grape/middleware/base_spec.rb +24 -15
- data/spec/grape/middleware/error_spec.rb +3 -3
- data/spec/grape/middleware/exception_spec.rb +111 -161
- data/spec/grape/middleware/formatter_spec.rb +28 -7
- data/spec/grape/middleware/globals_spec.rb +7 -4
- data/spec/grape/middleware/stack_spec.rb +15 -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 +14 -3
- data/spec/grape/validations/params_scope_spec.rb +72 -10
- data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
- data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
- 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 +248 -33
- data/spec/grape/validations/validators/default_spec.rb +121 -78
- data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
- data/spec/grape/validations/validators/except_values_spec.rb +4 -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 +342 -29
- data/spec/integration/eager_load/eager_load_spec.rb +15 -0
- 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 +32 -29
- data/spec/spec_helper.rb +12 -12
- data/spec/support/basic_auth_encode_helpers.rb +1 -1
- data/spec/support/chunks.rb +14 -0
- data/spec/support/versioned_helpers.rb +4 -6
- metadata +110 -102
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,6 +870,74 @@ XML
|
|
816
870
|
end
|
817
871
|
end
|
818
872
|
|
873
|
+
describe 'when hook behaviour is controlled by attributes on the route' do
|
874
|
+
before do
|
875
|
+
subject.before do
|
876
|
+
error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
|
877
|
+
end
|
878
|
+
|
879
|
+
subject.namespace 'example' do
|
880
|
+
before do
|
881
|
+
error!('Access Denied', 401) unless route.options[:namespace_secret] == params[:namespace_secret]
|
882
|
+
end
|
883
|
+
|
884
|
+
desc 'it gets with secret', secret: 'password'
|
885
|
+
get { status(params[:id] == '504' ? 200 : 404) }
|
886
|
+
|
887
|
+
desc 'it post with secret', secret: 'password', namespace_secret: 'namespace_password'
|
888
|
+
post {}
|
889
|
+
end
|
890
|
+
end
|
891
|
+
|
892
|
+
context 'when HTTP method is not defined' do
|
893
|
+
let(:response) { delete('/example') }
|
894
|
+
|
895
|
+
it 'responds with a 405 status' do
|
896
|
+
expect(response.status).to be 405
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
context 'when HTTP method is defined with attribute' do
|
901
|
+
let(:response) { post('/example?secret=incorrect_password') }
|
902
|
+
|
903
|
+
it 'responds with the defined error in the before hook' do
|
904
|
+
expect(response.status).to be 401
|
905
|
+
end
|
906
|
+
end
|
907
|
+
|
908
|
+
context 'when HTTP method is defined and the underlying before hook expectation is not met' do
|
909
|
+
let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
|
910
|
+
|
911
|
+
it 'ends up in the endpoint' do
|
912
|
+
expect(response.status).to be 401
|
913
|
+
end
|
914
|
+
end
|
915
|
+
|
916
|
+
context 'when HTTP method is defined and everything is like the before hooks expect' do
|
917
|
+
let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
|
918
|
+
|
919
|
+
it 'ends up in the endpoint' do
|
920
|
+
expect(response.status).to be 201
|
921
|
+
end
|
922
|
+
end
|
923
|
+
|
924
|
+
context 'when HEAD is called for the defined GET' do
|
925
|
+
let(:response) { head('/example?id=504') }
|
926
|
+
|
927
|
+
it 'responds with 401 because before expectations in before hooks are not met' do
|
928
|
+
expect(response.status).to be 401
|
929
|
+
end
|
930
|
+
end
|
931
|
+
|
932
|
+
context 'when HEAD is called for the defined GET' do
|
933
|
+
let(:response) { head('/example?id=504&secret=password') }
|
934
|
+
|
935
|
+
it 'responds with 200 because before hooks are not called' do
|
936
|
+
expect(response.status).to be 200
|
937
|
+
end
|
938
|
+
end
|
939
|
+
end
|
940
|
+
|
819
941
|
context 'allows HEAD on a GET request that' do
|
820
942
|
before do
|
821
943
|
subject.get 'example' do
|
@@ -828,7 +950,7 @@ XML
|
|
828
950
|
end
|
829
951
|
|
830
952
|
it 'returns a 200' do
|
831
|
-
expect(last_response.status).to
|
953
|
+
expect(last_response.status).to be 200
|
832
954
|
end
|
833
955
|
|
834
956
|
it 'has an empty body' do
|
@@ -844,31 +966,33 @@ XML
|
|
844
966
|
'example'
|
845
967
|
end
|
846
968
|
head '/example'
|
847
|
-
expect(last_response.status).to
|
969
|
+
expect(last_response.status).to be 400
|
848
970
|
end
|
849
971
|
end
|
850
972
|
|
851
973
|
context 'do_not_route_head!' do
|
852
|
-
before
|
974
|
+
before do
|
853
975
|
subject.do_not_route_head!
|
854
976
|
subject.get 'example' do
|
855
977
|
'example'
|
856
978
|
end
|
857
979
|
end
|
980
|
+
|
858
981
|
it 'options does not contain HEAD' do
|
859
982
|
options '/example'
|
860
|
-
expect(last_response.status).to
|
983
|
+
expect(last_response.status).to be 204
|
861
984
|
expect(last_response.body).to eql ''
|
862
985
|
expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'
|
863
986
|
end
|
987
|
+
|
864
988
|
it 'does not allow HEAD on a GET request' do
|
865
989
|
head '/example'
|
866
|
-
expect(last_response.status).to
|
990
|
+
expect(last_response.status).to be 405
|
867
991
|
end
|
868
992
|
end
|
869
993
|
|
870
994
|
context 'do_not_route_options!' do
|
871
|
-
before
|
995
|
+
before do
|
872
996
|
subject.do_not_route_options!
|
873
997
|
subject.get 'example' do
|
874
998
|
'example'
|
@@ -877,19 +1001,19 @@ XML
|
|
877
1001
|
|
878
1002
|
it 'does not create an OPTIONS route' do
|
879
1003
|
options '/example'
|
880
|
-
expect(last_response.status).to
|
1004
|
+
expect(last_response.status).to be 405
|
881
1005
|
end
|
882
1006
|
|
883
1007
|
it 'does not include OPTIONS in Allow header' do
|
884
1008
|
options '/example'
|
885
|
-
expect(last_response.status).to
|
1009
|
+
expect(last_response.status).to be 405
|
886
1010
|
expect(last_response.headers['Allow']).to eql 'GET, HEAD'
|
887
1011
|
end
|
888
1012
|
end
|
889
1013
|
|
890
1014
|
describe '.compile!' do
|
891
1015
|
it 'requires the grape/eager_load file' do
|
892
|
-
expect(app).to receive(:require).with('grape/eager_load')
|
1016
|
+
expect(app).to receive(:require).with('grape/eager_load').and_return(nil)
|
893
1017
|
app.compile!
|
894
1018
|
end
|
895
1019
|
|
@@ -911,7 +1035,7 @@ XML
|
|
911
1035
|
context 'when the app was mounted' do
|
912
1036
|
it 'returns the first mounted instance' do
|
913
1037
|
mounted_app = app
|
914
|
-
Class.new(
|
1038
|
+
Class.new(described_class) do
|
915
1039
|
namespace 'new_namespace' do
|
916
1040
|
mount mounted_app
|
917
1041
|
end
|
@@ -939,6 +1063,7 @@ XML
|
|
939
1063
|
end
|
940
1064
|
subject.namespace :blah do
|
941
1065
|
before { @foo = 'foo' }
|
1066
|
+
|
942
1067
|
get '/' do
|
943
1068
|
"blah - #{@foo}"
|
944
1069
|
end
|
@@ -980,7 +1105,7 @@ XML
|
|
980
1105
|
@var ||= 'default'
|
981
1106
|
end
|
982
1107
|
|
983
|
-
expect(m).to receive(:do_something!).
|
1108
|
+
expect(m).to receive(:do_something!).twice
|
984
1109
|
get '/'
|
985
1110
|
expect(last_response.body).to eql 'default'
|
986
1111
|
end
|
@@ -996,21 +1121,23 @@ XML
|
|
996
1121
|
end
|
997
1122
|
subject.resource ':id' do
|
998
1123
|
before { a.do_something! }
|
1124
|
+
|
999
1125
|
before_validation { b.do_something! }
|
1000
1126
|
after_validation { c.do_something! }
|
1001
1127
|
after { d.do_something! }
|
1128
|
+
|
1002
1129
|
get do
|
1003
1130
|
'got it'
|
1004
1131
|
end
|
1005
1132
|
end
|
1006
1133
|
|
1007
|
-
expect(a).to receive(:do_something!).
|
1008
|
-
expect(b).to receive(:do_something!).
|
1009
|
-
expect(c).to receive(:do_something!).
|
1010
|
-
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
|
1011
1138
|
|
1012
1139
|
get '/123'
|
1013
|
-
expect(last_response.status).to
|
1140
|
+
expect(last_response.status).to be 200
|
1014
1141
|
expect(last_response.body).to eql 'got it'
|
1015
1142
|
end
|
1016
1143
|
|
@@ -1025,21 +1152,23 @@ XML
|
|
1025
1152
|
end
|
1026
1153
|
subject.resource ':id' do
|
1027
1154
|
before { a.do_something! }
|
1155
|
+
|
1028
1156
|
before_validation { b.do_something! }
|
1029
1157
|
after_validation { c.do_something! }
|
1030
1158
|
after { d.do_something! }
|
1159
|
+
|
1031
1160
|
get do
|
1032
1161
|
'got it'
|
1033
1162
|
end
|
1034
1163
|
end
|
1035
1164
|
|
1036
|
-
expect(a).to receive(:do_something!).
|
1037
|
-
expect(b).to receive(:do_something!).
|
1165
|
+
expect(a).to receive(:do_something!).once
|
1166
|
+
expect(b).to receive(:do_something!).once
|
1038
1167
|
expect(c).to receive(:do_something!).exactly(0).times
|
1039
1168
|
expect(d).to receive(:do_something!).exactly(0).times
|
1040
1169
|
|
1041
1170
|
get '/abc'
|
1042
|
-
expect(last_response.status).to
|
1171
|
+
expect(last_response.status).to be 400
|
1043
1172
|
expect(last_response.body).to eql 'id is invalid'
|
1044
1173
|
end
|
1045
1174
|
|
@@ -1055,21 +1184,23 @@ XML
|
|
1055
1184
|
end
|
1056
1185
|
subject.resource ':id' do
|
1057
1186
|
before { a.here(i += 1) }
|
1187
|
+
|
1058
1188
|
before_validation { b.here(i += 1) }
|
1059
1189
|
after_validation { c.here(i += 1) }
|
1060
1190
|
after { d.here(i += 1) }
|
1191
|
+
|
1061
1192
|
get do
|
1062
1193
|
'got it'
|
1063
1194
|
end
|
1064
1195
|
end
|
1065
1196
|
|
1066
|
-
expect(a).to receive(:here).with(1).
|
1067
|
-
expect(b).to receive(:here).with(2).
|
1068
|
-
expect(c).to receive(:here).with(3).
|
1069
|
-
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
|
1070
1201
|
|
1071
1202
|
get '/123'
|
1072
|
-
expect(last_response.status).to
|
1203
|
+
expect(last_response.status).to be 200
|
1073
1204
|
expect(last_response.body).to eql 'got it'
|
1074
1205
|
end
|
1075
1206
|
end
|
@@ -1084,6 +1215,11 @@ XML
|
|
1084
1215
|
expect(last_response.headers['Content-Type']).to eq('text/plain')
|
1085
1216
|
end
|
1086
1217
|
|
1218
|
+
it 'does not set Cache-Control' do
|
1219
|
+
get '/foo'
|
1220
|
+
expect(last_response.headers['Cache-Control']).to eq(nil)
|
1221
|
+
end
|
1222
|
+
|
1087
1223
|
it 'sets content type for xml' do
|
1088
1224
|
get '/foo.xml'
|
1089
1225
|
expect(last_response.headers['Content-Type']).to eq('application/xml')
|
@@ -1155,7 +1291,7 @@ XML
|
|
1155
1291
|
subject.format :json
|
1156
1292
|
subject.get('/error') { error!('error in json', 500) }
|
1157
1293
|
get '/error.json'
|
1158
|
-
expect(last_response.status).to
|
1294
|
+
expect(last_response.status).to be 500
|
1159
1295
|
expect(last_response.headers['Content-Type']).to eql 'application/json'
|
1160
1296
|
end
|
1161
1297
|
|
@@ -1163,7 +1299,7 @@ XML
|
|
1163
1299
|
subject.format :xml
|
1164
1300
|
subject.get('/error') { error!('error in xml', 500) }
|
1165
1301
|
get '/error'
|
1166
|
-
expect(last_response.status).to
|
1302
|
+
expect(last_response.status).to be 500
|
1167
1303
|
expect(last_response.headers['Content-Type']).to eql 'application/xml'
|
1168
1304
|
end
|
1169
1305
|
|
@@ -1422,9 +1558,9 @@ XML
|
|
1422
1558
|
end
|
1423
1559
|
subject.get(:hello) { 'Hello, world.' }
|
1424
1560
|
get '/hello'
|
1425
|
-
expect(last_response.status).to
|
1561
|
+
expect(last_response.status).to be 401
|
1426
1562
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1427
|
-
expect(last_response.status).to
|
1563
|
+
expect(last_response.status).to be 200
|
1428
1564
|
end
|
1429
1565
|
|
1430
1566
|
it 'is scopable' do
|
@@ -1438,9 +1574,9 @@ XML
|
|
1438
1574
|
end
|
1439
1575
|
|
1440
1576
|
get '/hello'
|
1441
|
-
expect(last_response.status).to
|
1577
|
+
expect(last_response.status).to be 200
|
1442
1578
|
get '/admin/hello'
|
1443
|
-
expect(last_response.status).to
|
1579
|
+
expect(last_response.status).to be 401
|
1444
1580
|
end
|
1445
1581
|
|
1446
1582
|
it 'is callable via .auth as well' do
|
@@ -1450,9 +1586,9 @@ XML
|
|
1450
1586
|
|
1451
1587
|
subject.get(:hello) { 'Hello, world.' }
|
1452
1588
|
get '/hello'
|
1453
|
-
expect(last_response.status).to
|
1589
|
+
expect(last_response.status).to be 401
|
1454
1590
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1455
|
-
expect(last_response.status).to
|
1591
|
+
expect(last_response.status).to be 200
|
1456
1592
|
end
|
1457
1593
|
|
1458
1594
|
it 'has access to the current endpoint' do
|
@@ -1482,9 +1618,9 @@ XML
|
|
1482
1618
|
|
1483
1619
|
subject.get(:hello) { 'Hello, world.' }
|
1484
1620
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1485
|
-
expect(last_response.status).to
|
1621
|
+
expect(last_response.status).to be 200
|
1486
1622
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
|
1487
|
-
expect(last_response.status).to
|
1623
|
+
expect(last_response.status).to be 401
|
1488
1624
|
end
|
1489
1625
|
|
1490
1626
|
it 'can set instance variables accessible to routes' do
|
@@ -1496,39 +1632,36 @@ XML
|
|
1496
1632
|
|
1497
1633
|
subject.get(:hello) { @hello }
|
1498
1634
|
get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
|
1499
|
-
expect(last_response.status).to
|
1635
|
+
expect(last_response.status).to be 200
|
1500
1636
|
expect(last_response.body).to eql 'Hello, world.'
|
1501
1637
|
end
|
1502
1638
|
end
|
1503
1639
|
|
1504
1640
|
describe '.logger' do
|
1505
|
-
subject do
|
1506
|
-
Class.new(Grape::API) do
|
1507
|
-
def self.io
|
1508
|
-
@io ||= StringIO.new
|
1509
|
-
end
|
1510
|
-
logger ::Logger.new(io)
|
1511
|
-
end
|
1512
|
-
end
|
1513
|
-
|
1514
1641
|
it 'returns an instance of Logger class by default' do
|
1515
1642
|
expect(subject.logger.class).to eql Logger
|
1516
1643
|
end
|
1517
1644
|
|
1518
|
-
|
1519
|
-
|
1520
|
-
|
1521
|
-
|
1522
|
-
|
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
|
1654
|
+
|
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
|
1523
1660
|
end
|
1524
1661
|
|
1525
|
-
it '
|
1526
|
-
|
1527
|
-
|
1528
|
-
message = "this will be logged\n"
|
1529
|
-
message = "I, [#{Logger::Formatter.new.send(:format_datetime, t)}\##{Process.pid}] INFO -- : #{message}" if !defined?(Rails) || Gem::Version.new(Rails::VERSION::STRING) >= Gem::Version.new('4.0')
|
1530
|
-
expect(subject.io).to receive(:write).with(message)
|
1531
|
-
subject.logger.info 'this will be logged'
|
1662
|
+
it 'does not unnecessarily retain duplicate setup blocks' do
|
1663
|
+
subject.logger
|
1664
|
+
expect { subject.logger }.not_to change(subject.instance_variable_get(:@setup), :size)
|
1532
1665
|
end
|
1533
1666
|
end
|
1534
1667
|
|
@@ -1654,13 +1787,13 @@ XML
|
|
1654
1787
|
end
|
1655
1788
|
|
1656
1789
|
get '/new/abc'
|
1657
|
-
expect(last_response.status).to
|
1790
|
+
expect(last_response.status).to be 404
|
1658
1791
|
get '/legacy/abc'
|
1659
|
-
expect(last_response.status).to
|
1792
|
+
expect(last_response.status).to be 200
|
1660
1793
|
get '/legacy/def'
|
1661
|
-
expect(last_response.status).to
|
1794
|
+
expect(last_response.status).to be 404
|
1662
1795
|
get '/new/def'
|
1663
|
-
expect(last_response.status).to
|
1796
|
+
expect(last_response.status).to be 200
|
1664
1797
|
end
|
1665
1798
|
end
|
1666
1799
|
|
@@ -1880,8 +2013,8 @@ XML
|
|
1880
2013
|
end
|
1881
2014
|
|
1882
2015
|
context 'with multiple apis' do
|
1883
|
-
let(:a) { Class.new(
|
1884
|
-
let(:b) { Class.new(
|
2016
|
+
let(:a) { Class.new(described_class) }
|
2017
|
+
let(:b) { Class.new(described_class) }
|
1885
2018
|
|
1886
2019
|
before do
|
1887
2020
|
a.helpers do
|
@@ -1915,7 +2048,7 @@ XML
|
|
1915
2048
|
raise 'rain!'
|
1916
2049
|
end
|
1917
2050
|
get '/exception'
|
1918
|
-
expect(last_response.status).to
|
2051
|
+
expect(last_response.status).to be 500
|
1919
2052
|
expect(last_response.body).to eq 'rain!'
|
1920
2053
|
end
|
1921
2054
|
|
@@ -1927,7 +2060,7 @@ XML
|
|
1927
2060
|
raise 'rain!'
|
1928
2061
|
end
|
1929
2062
|
get '/exception'
|
1930
|
-
expect(last_response.status).to
|
2063
|
+
expect(last_response.status).to be 500
|
1931
2064
|
expect(last_response.body).to eq({ error: 'rain!' }.to_json)
|
1932
2065
|
end
|
1933
2066
|
|
@@ -1937,7 +2070,7 @@ XML
|
|
1937
2070
|
subject.get('/unrescued') { raise 'beefcake' }
|
1938
2071
|
|
1939
2072
|
get '/rescued'
|
1940
|
-
expect(last_response.status).to
|
2073
|
+
expect(last_response.status).to be 500
|
1941
2074
|
|
1942
2075
|
expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
|
1943
2076
|
end
|
@@ -1956,10 +2089,10 @@ XML
|
|
1956
2089
|
subject.get('/standard_error') { raise StandardError }
|
1957
2090
|
|
1958
2091
|
get '/child_of_standard_error'
|
1959
|
-
expect(last_response.status).to
|
2092
|
+
expect(last_response.status).to be 402
|
1960
2093
|
|
1961
2094
|
get '/standard_error'
|
1962
|
-
expect(last_response.status).to
|
2095
|
+
expect(last_response.status).to be 401
|
1963
2096
|
end
|
1964
2097
|
|
1965
2098
|
context 'CustomError subclass of Grape::Exceptions::Base' do
|
@@ -2000,7 +2133,7 @@ XML
|
|
2000
2133
|
subject.get('/formatter_exception') { 'Hello world' }
|
2001
2134
|
|
2002
2135
|
get '/formatter_exception'
|
2003
|
-
expect(last_response.status).to
|
2136
|
+
expect(last_response.status).to be 500
|
2004
2137
|
expect(last_response.body).to eq('Formatter Error')
|
2005
2138
|
end
|
2006
2139
|
|
@@ -2010,7 +2143,7 @@ XML
|
|
2010
2143
|
|
2011
2144
|
expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
|
2012
2145
|
get '/'
|
2013
|
-
expect(last_response.status).to
|
2146
|
+
expect(last_response.status).to be 500
|
2014
2147
|
expect(last_response.body).to eql 'Invalid response'
|
2015
2148
|
end
|
2016
2149
|
end
|
@@ -2024,14 +2157,16 @@ XML
|
|
2024
2157
|
raise 'rain!'
|
2025
2158
|
end
|
2026
2159
|
get '/exception'
|
2027
|
-
expect(last_response.status).to
|
2160
|
+
expect(last_response.status).to be 202
|
2028
2161
|
expect(last_response.body).to eq('rescued from rain!')
|
2029
2162
|
end
|
2030
2163
|
|
2031
2164
|
context 'custom errors' do
|
2032
2165
|
before do
|
2033
2166
|
class ConnectionError < RuntimeError; end
|
2167
|
+
|
2034
2168
|
class DatabaseError < RuntimeError; end
|
2169
|
+
|
2035
2170
|
class CommunicationError < StandardError; end
|
2036
2171
|
end
|
2037
2172
|
|
@@ -2043,9 +2178,10 @@ XML
|
|
2043
2178
|
raise ConnectionError
|
2044
2179
|
end
|
2045
2180
|
get '/exception'
|
2046
|
-
expect(last_response.status).to
|
2181
|
+
expect(last_response.status).to be 500
|
2047
2182
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2048
2183
|
end
|
2184
|
+
|
2049
2185
|
it 'rescues a specific error' do
|
2050
2186
|
subject.rescue_from ConnectionError do |e|
|
2051
2187
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2054,9 +2190,10 @@ XML
|
|
2054
2190
|
raise ConnectionError
|
2055
2191
|
end
|
2056
2192
|
get '/exception'
|
2057
|
-
expect(last_response.status).to
|
2193
|
+
expect(last_response.status).to be 500
|
2058
2194
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2059
2195
|
end
|
2196
|
+
|
2060
2197
|
it 'rescues a subclass of an error by default' do
|
2061
2198
|
subject.rescue_from RuntimeError do |e|
|
2062
2199
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2065,9 +2202,10 @@ XML
|
|
2065
2202
|
raise ConnectionError
|
2066
2203
|
end
|
2067
2204
|
get '/exception'
|
2068
|
-
expect(last_response.status).to
|
2205
|
+
expect(last_response.status).to be 500
|
2069
2206
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2070
2207
|
end
|
2208
|
+
|
2071
2209
|
it 'rescues multiple specific errors' do
|
2072
2210
|
subject.rescue_from ConnectionError do |e|
|
2073
2211
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2082,12 +2220,13 @@ XML
|
|
2082
2220
|
raise DatabaseError
|
2083
2221
|
end
|
2084
2222
|
get '/connection'
|
2085
|
-
expect(last_response.status).to
|
2223
|
+
expect(last_response.status).to be 500
|
2086
2224
|
expect(last_response.body).to eq('rescued from ConnectionError')
|
2087
2225
|
get '/database'
|
2088
|
-
expect(last_response.status).to
|
2226
|
+
expect(last_response.status).to be 500
|
2089
2227
|
expect(last_response.body).to eq('rescued from DatabaseError')
|
2090
2228
|
end
|
2229
|
+
|
2091
2230
|
it 'does not rescue a different error' do
|
2092
2231
|
subject.rescue_from RuntimeError do |e|
|
2093
2232
|
rack_response("rescued from #{e.class.name}", 500)
|
@@ -2153,7 +2292,7 @@ XML
|
|
2153
2292
|
subject.rescue_from :all, with: :not_exist_method
|
2154
2293
|
subject.get('/rescue_method') { raise StandardError }
|
2155
2294
|
|
2156
|
-
expect { get '/rescue_method' }.to raise_error(NoMethodError,
|
2295
|
+
expect { get '/rescue_method' }.to raise_error(NoMethodError, /^undefined method `not_exist_method'/)
|
2157
2296
|
end
|
2158
2297
|
|
2159
2298
|
it 'correctly chooses exception handler if :all handler is specified' do
|
@@ -2187,6 +2326,7 @@ XML
|
|
2187
2326
|
module ApiSpec
|
2188
2327
|
module APIErrors
|
2189
2328
|
class ParentError < StandardError; end
|
2329
|
+
|
2190
2330
|
class ChildError < ParentError; end
|
2191
2331
|
end
|
2192
2332
|
end
|
@@ -2207,9 +2347,9 @@ XML
|
|
2207
2347
|
end
|
2208
2348
|
|
2209
2349
|
get '/caught_child'
|
2210
|
-
expect(last_response.status).to
|
2350
|
+
expect(last_response.status).to be 500
|
2211
2351
|
get '/caught_parent'
|
2212
|
-
expect(last_response.status).to
|
2352
|
+
expect(last_response.status).to be 500
|
2213
2353
|
expect { get '/uncaught_parent' }.to raise_error(StandardError)
|
2214
2354
|
end
|
2215
2355
|
|
@@ -2222,7 +2362,7 @@ XML
|
|
2222
2362
|
end
|
2223
2363
|
|
2224
2364
|
get '/caught_child'
|
2225
|
-
expect(last_response.status).to
|
2365
|
+
expect(last_response.status).to be 500
|
2226
2366
|
end
|
2227
2367
|
|
2228
2368
|
it 'does not rescue child errors if rescue_subclasses is false' do
|
@@ -2317,7 +2457,7 @@ XML
|
|
2317
2457
|
end
|
2318
2458
|
|
2319
2459
|
context 'class' do
|
2320
|
-
before
|
2460
|
+
before do
|
2321
2461
|
module ApiSpec
|
2322
2462
|
class CustomErrorFormatter
|
2323
2463
|
def self.call(message, _backtrace, _options, _env, _original_exception)
|
@@ -2326,6 +2466,7 @@ XML
|
|
2326
2466
|
end
|
2327
2467
|
end
|
2328
2468
|
end
|
2469
|
+
|
2329
2470
|
it 'returns a custom error format' do
|
2330
2471
|
subject.rescue_from :all, backtrace: true
|
2331
2472
|
subject.error_formatter :txt, ApiSpec::CustomErrorFormatter
|
@@ -2339,7 +2480,7 @@ XML
|
|
2339
2480
|
|
2340
2481
|
describe 'with' do
|
2341
2482
|
context 'class' do
|
2342
|
-
before
|
2483
|
+
before do
|
2343
2484
|
module ApiSpec
|
2344
2485
|
class CustomErrorFormatter
|
2345
2486
|
def self.call(message, _backtrace, _option, _env, _original_exception)
|
@@ -2369,6 +2510,7 @@ XML
|
|
2369
2510
|
get '/exception'
|
2370
2511
|
expect(last_response.body).to eql '{"error":"rain!"}'
|
2371
2512
|
end
|
2513
|
+
|
2372
2514
|
it 'rescues all errors and return :json with backtrace' do
|
2373
2515
|
subject.rescue_from :all, backtrace: true
|
2374
2516
|
subject.format :json
|
@@ -2380,6 +2522,7 @@ XML
|
|
2380
2522
|
expect(json['error']).to eql 'rain!'
|
2381
2523
|
expect(json['backtrace'].length).to be > 0
|
2382
2524
|
end
|
2525
|
+
|
2383
2526
|
it 'rescues error! and return txt' do
|
2384
2527
|
subject.format :txt
|
2385
2528
|
subject.get '/error' do
|
@@ -2388,23 +2531,26 @@ XML
|
|
2388
2531
|
get '/error'
|
2389
2532
|
expect(last_response.body).to eql 'Access Denied'
|
2390
2533
|
end
|
2534
|
+
|
2391
2535
|
context 'with json format' do
|
2392
2536
|
before { subject.format :json }
|
2393
2537
|
|
2538
|
+
after do
|
2539
|
+
get '/error'
|
2540
|
+
expect(last_response.body).to eql('{"error":"failure"}')
|
2541
|
+
end
|
2542
|
+
|
2394
2543
|
it 'rescues error! called with a string and returns json' do
|
2395
2544
|
subject.get('/error') { error!(:failure, 401) }
|
2396
2545
|
end
|
2546
|
+
|
2397
2547
|
it 'rescues error! called with a symbol and returns json' do
|
2398
2548
|
subject.get('/error') { error!(:failure, 401) }
|
2399
2549
|
end
|
2550
|
+
|
2400
2551
|
it 'rescues error! called with a hash and returns json' do
|
2401
2552
|
subject.get('/error') { error!({ error: :failure }, 401) }
|
2402
2553
|
end
|
2403
|
-
|
2404
|
-
after do
|
2405
|
-
get '/error'
|
2406
|
-
expect(last_response.body).to eql('{"error":"failure"}')
|
2407
|
-
end
|
2408
2554
|
end
|
2409
2555
|
end
|
2410
2556
|
|
@@ -2417,6 +2563,7 @@ XML
|
|
2417
2563
|
get '/excel.xls'
|
2418
2564
|
expect(last_response.content_type).to eq('application/vnd.ms-excel')
|
2419
2565
|
end
|
2566
|
+
|
2420
2567
|
it 'allows to override content-type' do
|
2421
2568
|
subject.get :content do
|
2422
2569
|
content_type 'text/javascript'
|
@@ -2425,6 +2572,7 @@ XML
|
|
2425
2572
|
get '/content'
|
2426
2573
|
expect(last_response.content_type).to eq('text/javascript')
|
2427
2574
|
end
|
2575
|
+
|
2428
2576
|
it 'removes existing content types' do
|
2429
2577
|
subject.content_type :xls, 'application/vnd.ms-excel'
|
2430
2578
|
subject.get :excel do
|
@@ -2442,24 +2590,27 @@ XML
|
|
2442
2590
|
|
2443
2591
|
describe '.formatter' do
|
2444
2592
|
context 'multiple formatters' do
|
2445
|
-
before
|
2593
|
+
before do
|
2446
2594
|
subject.formatter :json, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
2447
2595
|
subject.formatter :txt, ->(object, _env) { "custom_formatter: #{object[:some]}" }
|
2448
2596
|
subject.get :simple do
|
2449
2597
|
{ some: 'hash' }
|
2450
2598
|
end
|
2451
2599
|
end
|
2600
|
+
|
2452
2601
|
it 'sets one formatter' do
|
2453
2602
|
get '/simple.json'
|
2454
2603
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2455
2604
|
end
|
2605
|
+
|
2456
2606
|
it 'sets another formatter' do
|
2457
2607
|
get '/simple.txt'
|
2458
2608
|
expect(last_response.body).to eql 'custom_formatter: hash'
|
2459
2609
|
end
|
2460
2610
|
end
|
2611
|
+
|
2461
2612
|
context 'custom formatter' do
|
2462
|
-
before
|
2613
|
+
before do
|
2463
2614
|
subject.content_type :json, 'application/json'
|
2464
2615
|
subject.content_type :custom, 'application/custom'
|
2465
2616
|
subject.formatter :custom, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
|
@@ -2467,15 +2618,18 @@ XML
|
|
2467
2618
|
{ some: 'hash' }
|
2468
2619
|
end
|
2469
2620
|
end
|
2621
|
+
|
2470
2622
|
it 'uses json' do
|
2471
2623
|
get '/simple.json'
|
2472
2624
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2473
2625
|
end
|
2626
|
+
|
2474
2627
|
it 'uses custom formatter' do
|
2475
2628
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2476
2629
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
2477
2630
|
end
|
2478
2631
|
end
|
2632
|
+
|
2479
2633
|
context 'custom formatter class' do
|
2480
2634
|
module ApiSpec
|
2481
2635
|
module CustomFormatter
|
@@ -2484,7 +2638,7 @@ XML
|
|
2484
2638
|
end
|
2485
2639
|
end
|
2486
2640
|
end
|
2487
|
-
before
|
2641
|
+
before do
|
2488
2642
|
subject.content_type :json, 'application/json'
|
2489
2643
|
subject.content_type :custom, 'application/custom'
|
2490
2644
|
subject.formatter :custom, ApiSpec::CustomFormatter
|
@@ -2492,10 +2646,12 @@ XML
|
|
2492
2646
|
{ some: 'hash' }
|
2493
2647
|
end
|
2494
2648
|
end
|
2649
|
+
|
2495
2650
|
it 'uses json' do
|
2496
2651
|
get '/simple.json'
|
2497
2652
|
expect(last_response.body).to eql '{"some":"hash"}'
|
2498
2653
|
end
|
2654
|
+
|
2499
2655
|
it 'uses custom formatter' do
|
2500
2656
|
get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
|
2501
2657
|
expect(last_response.body).to eql '{"custom_formatter":"hash"}'
|
@@ -2513,8 +2669,9 @@ XML
|
|
2513
2669
|
expect(last_response.status).to eq(201)
|
2514
2670
|
expect(last_response.body).to eq('{"x":42}')
|
2515
2671
|
end
|
2672
|
+
|
2516
2673
|
context 'lambda parser' do
|
2517
|
-
before
|
2674
|
+
before do
|
2518
2675
|
subject.content_type :txt, 'text/plain'
|
2519
2676
|
subject.content_type :custom, 'text/custom'
|
2520
2677
|
subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }
|
@@ -2522,6 +2679,7 @@ XML
|
|
2522
2679
|
params[:simple]
|
2523
2680
|
end
|
2524
2681
|
end
|
2682
|
+
|
2525
2683
|
['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
|
2526
2684
|
it "uses parser for #{content_type}" do
|
2527
2685
|
put '/simple', 'simple', 'CONTENT_TYPE' => content_type
|
@@ -2530,6 +2688,7 @@ XML
|
|
2530
2688
|
end
|
2531
2689
|
end
|
2532
2690
|
end
|
2691
|
+
|
2533
2692
|
context 'custom parser class' do
|
2534
2693
|
module ApiSpec
|
2535
2694
|
module CustomParser
|
@@ -2538,7 +2697,7 @@ XML
|
|
2538
2697
|
end
|
2539
2698
|
end
|
2540
2699
|
end
|
2541
|
-
before
|
2700
|
+
before do
|
2542
2701
|
subject.content_type :txt, 'text/plain'
|
2543
2702
|
subject.content_type :custom, 'text/custom'
|
2544
2703
|
subject.parser :custom, ApiSpec::CustomParser
|
@@ -2546,12 +2705,14 @@ XML
|
|
2546
2705
|
params[:simple]
|
2547
2706
|
end
|
2548
2707
|
end
|
2708
|
+
|
2549
2709
|
it 'uses custom parser' do
|
2550
2710
|
put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
|
2551
2711
|
expect(last_response.status).to eq(200)
|
2552
2712
|
expect(last_response.body).to eql 'elpmis'
|
2553
2713
|
end
|
2554
2714
|
end
|
2715
|
+
|
2555
2716
|
if Object.const_defined? :MultiXml
|
2556
2717
|
context 'multi_xml' do
|
2557
2718
|
it "doesn't parse yaml" do
|
@@ -2576,12 +2737,13 @@ XML
|
|
2576
2737
|
end
|
2577
2738
|
end
|
2578
2739
|
context 'none parser class' do
|
2579
|
-
before
|
2740
|
+
before do
|
2580
2741
|
subject.parser :json, nil
|
2581
2742
|
subject.put 'data' do
|
2582
2743
|
"body: #{env['api.request.body']}"
|
2583
2744
|
end
|
2584
2745
|
end
|
2746
|
+
|
2585
2747
|
it 'does not parse data' do
|
2586
2748
|
put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
|
2587
2749
|
expect(last_response.status).to eq(200)
|
@@ -2591,10 +2753,11 @@ XML
|
|
2591
2753
|
end
|
2592
2754
|
|
2593
2755
|
describe '.default_format' do
|
2594
|
-
before
|
2756
|
+
before do
|
2595
2757
|
subject.format :json
|
2596
2758
|
subject.default_format :json
|
2597
2759
|
end
|
2760
|
+
|
2598
2761
|
it 'returns data in default format' do
|
2599
2762
|
subject.get '/data' do
|
2600
2763
|
{ x: 42 }
|
@@ -2603,6 +2766,7 @@ XML
|
|
2603
2766
|
expect(last_response.status).to eq(200)
|
2604
2767
|
expect(last_response.body).to eq('{"x":42}')
|
2605
2768
|
end
|
2769
|
+
|
2606
2770
|
it 'parses data in default format' do
|
2607
2771
|
subject.post '/data' do
|
2608
2772
|
{ x: params[:x] }
|
@@ -2621,16 +2785,18 @@ XML
|
|
2621
2785
|
raise 'rain!'
|
2622
2786
|
end
|
2623
2787
|
get '/exception'
|
2624
|
-
expect(last_response.status).to
|
2788
|
+
expect(last_response.status).to be 200
|
2625
2789
|
end
|
2790
|
+
|
2626
2791
|
it 'has a default error status' do
|
2627
2792
|
subject.rescue_from :all
|
2628
2793
|
subject.get '/exception' do
|
2629
2794
|
raise 'rain!'
|
2630
2795
|
end
|
2631
2796
|
get '/exception'
|
2632
|
-
expect(last_response.status).to
|
2797
|
+
expect(last_response.status).to be 500
|
2633
2798
|
end
|
2799
|
+
|
2634
2800
|
it 'uses the default error status in error!' do
|
2635
2801
|
subject.rescue_from :all
|
2636
2802
|
subject.default_error_status 400
|
@@ -2638,7 +2804,7 @@ XML
|
|
2638
2804
|
error! 'rain!'
|
2639
2805
|
end
|
2640
2806
|
get '/exception'
|
2641
|
-
expect(last_response.status).to
|
2807
|
+
expect(last_response.status).to be 400
|
2642
2808
|
end
|
2643
2809
|
end
|
2644
2810
|
|
@@ -2664,7 +2830,7 @@ XML
|
|
2664
2830
|
end
|
2665
2831
|
|
2666
2832
|
get '/exception'
|
2667
|
-
expect(last_response.status).to
|
2833
|
+
expect(last_response.status).to be 408
|
2668
2834
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2669
2835
|
end
|
2670
2836
|
|
@@ -2675,7 +2841,7 @@ XML
|
|
2675
2841
|
end
|
2676
2842
|
|
2677
2843
|
get '/exception'
|
2678
|
-
expect(last_response.status).to
|
2844
|
+
expect(last_response.status).to be 408
|
2679
2845
|
expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
|
2680
2846
|
end
|
2681
2847
|
end
|
@@ -2686,12 +2852,14 @@ XML
|
|
2686
2852
|
expect(subject.routes).to eq([])
|
2687
2853
|
end
|
2688
2854
|
end
|
2855
|
+
|
2689
2856
|
describe 'single method api structure' do
|
2690
|
-
before
|
2857
|
+
before do
|
2691
2858
|
subject.get :ping do
|
2692
2859
|
'pong'
|
2693
2860
|
end
|
2694
2861
|
end
|
2862
|
+
|
2695
2863
|
it 'returns one route' do
|
2696
2864
|
expect(subject.routes.size).to eq(1)
|
2697
2865
|
route = subject.routes[0]
|
@@ -2700,8 +2868,9 @@ XML
|
|
2700
2868
|
expect(route.request_method).to eq('GET')
|
2701
2869
|
end
|
2702
2870
|
end
|
2871
|
+
|
2703
2872
|
describe 'api structure with two versions and a namespace' do
|
2704
|
-
before
|
2873
|
+
before do
|
2705
2874
|
subject.version 'v1', using: :path
|
2706
2875
|
subject.get 'version' do
|
2707
2876
|
api.version
|
@@ -2717,30 +2886,37 @@ XML
|
|
2717
2886
|
end
|
2718
2887
|
end
|
2719
2888
|
end
|
2889
|
+
|
2720
2890
|
it 'returns the latest version set' do
|
2721
2891
|
expect(subject.version).to eq('v2')
|
2722
2892
|
end
|
2893
|
+
|
2723
2894
|
it 'returns versions' do
|
2724
2895
|
expect(subject.versions).to eq(%w[v1 v2])
|
2725
2896
|
end
|
2897
|
+
|
2726
2898
|
it 'sets route paths' do
|
2727
2899
|
expect(subject.routes.size).to be >= 2
|
2728
2900
|
expect(subject.routes[0].path).to eq('/:version/version(.:format)')
|
2729
2901
|
expect(subject.routes[1].path).to eq('/p/:version/n1/n2/version(.:format)')
|
2730
2902
|
end
|
2903
|
+
|
2731
2904
|
it 'sets route versions' do
|
2732
2905
|
expect(subject.routes[0].version).to eq('v1')
|
2733
2906
|
expect(subject.routes[1].version).to eq('v2')
|
2734
2907
|
end
|
2908
|
+
|
2735
2909
|
it 'sets a nested namespace' do
|
2736
2910
|
expect(subject.routes[1].namespace).to eq('/n1/n2')
|
2737
2911
|
end
|
2912
|
+
|
2738
2913
|
it 'sets prefix' do
|
2739
2914
|
expect(subject.routes[1].prefix).to eq('p')
|
2740
2915
|
end
|
2741
2916
|
end
|
2917
|
+
|
2742
2918
|
describe 'api structure with additional parameters' do
|
2743
|
-
before
|
2919
|
+
before do
|
2744
2920
|
subject.params do
|
2745
2921
|
requires :token, desc: 'a token'
|
2746
2922
|
optional :limit, desc: 'the limit'
|
@@ -2749,14 +2925,17 @@ XML
|
|
2749
2925
|
params[:string].split(params[:token], (params[:limit] || 0).to_i)
|
2750
2926
|
end
|
2751
2927
|
end
|
2928
|
+
|
2752
2929
|
it 'splits a string' do
|
2753
2930
|
get '/split/a,b,c.json', token: ','
|
2754
2931
|
expect(last_response.body).to eq('["a","b","c"]')
|
2755
2932
|
end
|
2933
|
+
|
2756
2934
|
it 'splits a string with limit' do
|
2757
2935
|
get '/split/a,b,c.json', token: ',', limit: '2'
|
2758
2936
|
expect(last_response.body).to eq('["a","b,c"]')
|
2759
2937
|
end
|
2938
|
+
|
2760
2939
|
it 'sets params' do
|
2761
2940
|
expect(subject.routes.map do |route|
|
2762
2941
|
{ params: route.params }
|
@@ -2771,8 +2950,9 @@ XML
|
|
2771
2950
|
]
|
2772
2951
|
end
|
2773
2952
|
end
|
2953
|
+
|
2774
2954
|
describe 'api structure with multiple apis' do
|
2775
|
-
before
|
2955
|
+
before do
|
2776
2956
|
subject.params do
|
2777
2957
|
requires :one, desc: 'a token'
|
2778
2958
|
optional :two, desc: 'the limit'
|
@@ -2787,6 +2967,7 @@ XML
|
|
2787
2967
|
subject.get 'two' do
|
2788
2968
|
end
|
2789
2969
|
end
|
2970
|
+
|
2790
2971
|
it 'sets params' do
|
2791
2972
|
expect(subject.routes.map do |route|
|
2792
2973
|
{ params: route.params }
|
@@ -2806,8 +2987,9 @@ XML
|
|
2806
2987
|
]
|
2807
2988
|
end
|
2808
2989
|
end
|
2990
|
+
|
2809
2991
|
describe 'api structure with an api without params' do
|
2810
|
-
before
|
2992
|
+
before do
|
2811
2993
|
subject.params do
|
2812
2994
|
requires :one, desc: 'a token'
|
2813
2995
|
optional :two, desc: 'the limit'
|
@@ -2818,6 +3000,7 @@ XML
|
|
2818
3000
|
subject.get 'two' do
|
2819
3001
|
end
|
2820
3002
|
end
|
3003
|
+
|
2821
3004
|
it 'sets params' do
|
2822
3005
|
expect(subject.routes.map do |route|
|
2823
3006
|
{ params: route.params }
|
@@ -2834,17 +3017,20 @@ XML
|
|
2834
3017
|
]
|
2835
3018
|
end
|
2836
3019
|
end
|
3020
|
+
|
2837
3021
|
describe 'api with a custom route setting' do
|
2838
|
-
before
|
3022
|
+
before do
|
2839
3023
|
subject.route_setting :custom, key: 'value'
|
2840
3024
|
subject.get 'one'
|
2841
3025
|
end
|
3026
|
+
|
2842
3027
|
it 'exposed' do
|
2843
3028
|
expect(subject.routes.count).to eq 1
|
2844
3029
|
route = subject.routes.first
|
2845
3030
|
expect(route.settings[:custom]).to eq(key: 'value')
|
2846
3031
|
end
|
2847
3032
|
end
|
3033
|
+
|
2848
3034
|
describe 'status' do
|
2849
3035
|
it 'can be set to arbitrary Integer value' do
|
2850
3036
|
subject.get '/foo' do
|
@@ -2853,6 +3039,7 @@ XML
|
|
2853
3039
|
get '/foo'
|
2854
3040
|
expect(last_response.status).to eq 210
|
2855
3041
|
end
|
3042
|
+
|
2856
3043
|
it 'can be set with a status code symbol' do
|
2857
3044
|
subject.get '/foo' do
|
2858
3045
|
status :see_other
|
@@ -2867,10 +3054,12 @@ XML
|
|
2867
3054
|
it 'empty array of routes' do
|
2868
3055
|
expect(subject.routes).to eq([])
|
2869
3056
|
end
|
3057
|
+
|
2870
3058
|
it 'empty array of routes' do
|
2871
3059
|
subject.desc 'grape api'
|
2872
3060
|
expect(subject.routes).to eq([])
|
2873
3061
|
end
|
3062
|
+
|
2874
3063
|
it 'describes a method' do
|
2875
3064
|
subject.desc 'first method'
|
2876
3065
|
subject.get :first
|
@@ -2881,6 +3070,7 @@ XML
|
|
2881
3070
|
expect(route.params).to eq({})
|
2882
3071
|
expect(route.options).to be_a_kind_of(Hash)
|
2883
3072
|
end
|
3073
|
+
|
2884
3074
|
it 'has params which does not include format and version as named captures' do
|
2885
3075
|
subject.version :v1, using: :path
|
2886
3076
|
subject.get :first
|
@@ -2888,6 +3078,7 @@ XML
|
|
2888
3078
|
expect(param_keys).not_to include('format')
|
2889
3079
|
expect(param_keys).not_to include('version')
|
2890
3080
|
end
|
3081
|
+
|
2891
3082
|
it 'describes methods separately' do
|
2892
3083
|
subject.desc 'first method'
|
2893
3084
|
subject.get :first
|
@@ -2901,6 +3092,7 @@ XML
|
|
2901
3092
|
{ description: 'second method', params: {} }
|
2902
3093
|
]
|
2903
3094
|
end
|
3095
|
+
|
2904
3096
|
it 'resets desc' do
|
2905
3097
|
subject.desc 'first method'
|
2906
3098
|
subject.get :first
|
@@ -2912,6 +3104,7 @@ XML
|
|
2912
3104
|
{ description: nil, params: {} }
|
2913
3105
|
]
|
2914
3106
|
end
|
3107
|
+
|
2915
3108
|
it 'namespaces and describe arbitrary parameters' do
|
2916
3109
|
subject.namespace 'ns' do
|
2917
3110
|
desc 'ns second', foo: 'bar'
|
@@ -2923,6 +3116,7 @@ XML
|
|
2923
3116
|
{ description: 'ns second', foo: 'bar', params: {} }
|
2924
3117
|
]
|
2925
3118
|
end
|
3119
|
+
|
2926
3120
|
it 'includes details' do
|
2927
3121
|
subject.desc 'method', details: 'method details'
|
2928
3122
|
subject.get 'method'
|
@@ -2932,6 +3126,7 @@ XML
|
|
2932
3126
|
{ description: 'method', details: 'method details', params: {} }
|
2933
3127
|
]
|
2934
3128
|
end
|
3129
|
+
|
2935
3130
|
it 'describes a method with parameters' do
|
2936
3131
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
2937
3132
|
subject.get 'reverse' do
|
@@ -2943,6 +3138,7 @@ XML
|
|
2943
3138
|
{ description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
|
2944
3139
|
]
|
2945
3140
|
end
|
3141
|
+
|
2946
3142
|
it 'does not inherit param descriptions in consequent namespaces' do
|
2947
3143
|
subject.desc 'global description'
|
2948
3144
|
subject.params do
|
@@ -2973,6 +3169,7 @@ XML
|
|
2973
3169
|
} }
|
2974
3170
|
]
|
2975
3171
|
end
|
3172
|
+
|
2976
3173
|
it 'merges the parameters of the namespace with the parameters of the method' do
|
2977
3174
|
subject.desc 'namespace'
|
2978
3175
|
subject.params do
|
@@ -2997,6 +3194,7 @@ XML
|
|
2997
3194
|
} }
|
2998
3195
|
]
|
2999
3196
|
end
|
3197
|
+
|
3000
3198
|
it 'merges the parameters of nested namespaces' do
|
3001
3199
|
subject.desc 'ns1'
|
3002
3200
|
subject.params do
|
@@ -3029,6 +3227,7 @@ XML
|
|
3029
3227
|
} }
|
3030
3228
|
]
|
3031
3229
|
end
|
3230
|
+
|
3032
3231
|
it 'groups nested params and prevents overwriting of params with same name in different groups' do
|
3033
3232
|
subject.desc 'method'
|
3034
3233
|
subject.params do
|
@@ -3044,14 +3243,15 @@ XML
|
|
3044
3243
|
subject.get 'method'
|
3045
3244
|
|
3046
3245
|
expect(subject.routes.map(&:params)).to eq [{
|
3047
|
-
'group1'
|
3246
|
+
'group1' => { required: true, type: 'Array' },
|
3048
3247
|
'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
|
3049
3248
|
'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
|
3050
|
-
'group2'
|
3249
|
+
'group2' => { required: true, type: 'Array' },
|
3051
3250
|
'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
|
3052
3251
|
'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
|
3053
3252
|
}]
|
3054
3253
|
end
|
3254
|
+
|
3055
3255
|
it 'uses full name of parameters in nested groups' do
|
3056
3256
|
subject.desc 'nesting'
|
3057
3257
|
subject.params do
|
@@ -3072,6 +3272,7 @@ XML
|
|
3072
3272
|
} }
|
3073
3273
|
]
|
3074
3274
|
end
|
3275
|
+
|
3075
3276
|
it 'allows to set the type attribute on :group element' do
|
3076
3277
|
subject.params do
|
3077
3278
|
group :foo, type: Array do
|
@@ -3079,6 +3280,7 @@ XML
|
|
3079
3280
|
end
|
3080
3281
|
end
|
3081
3282
|
end
|
3283
|
+
|
3082
3284
|
it 'parses parameters when no description is given' do
|
3083
3285
|
subject.params do
|
3084
3286
|
requires :one_param, desc: 'one param'
|
@@ -3090,6 +3292,7 @@ XML
|
|
3090
3292
|
{ description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
|
3091
3293
|
]
|
3092
3294
|
end
|
3295
|
+
|
3093
3296
|
it 'does not symbolize params' do
|
3094
3297
|
subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
|
3095
3298
|
subject.get 'reverse/:s' do
|
@@ -3148,7 +3351,7 @@ XML
|
|
3148
3351
|
subject.version 'v1', using: :path
|
3149
3352
|
|
3150
3353
|
subject.namespace :cool do
|
3151
|
-
app = Class.new(Grape::API)
|
3354
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3152
3355
|
app.get('/awesome') do
|
3153
3356
|
'yo'
|
3154
3357
|
end
|
@@ -3164,12 +3367,12 @@ XML
|
|
3164
3367
|
subject.version 'v1', using: :path
|
3165
3368
|
|
3166
3369
|
subject.namespace :cool do
|
3167
|
-
inner_app = Class.new(Grape::API)
|
3370
|
+
inner_app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3168
3371
|
inner_app.get('/awesome') do
|
3169
3372
|
'yo'
|
3170
3373
|
end
|
3171
3374
|
|
3172
|
-
app = Class.new(Grape::API)
|
3375
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3173
3376
|
app.mount inner_app
|
3174
3377
|
mount app
|
3175
3378
|
end
|
@@ -3184,7 +3387,7 @@ XML
|
|
3184
3387
|
rack_response("rescued from #{e.message}", 202)
|
3185
3388
|
end
|
3186
3389
|
|
3187
|
-
app = Class.new(
|
3390
|
+
app = Class.new(described_class)
|
3188
3391
|
|
3189
3392
|
subject.namespace :mounted do
|
3190
3393
|
app.rescue_from ArgumentError
|
@@ -3193,15 +3396,16 @@ XML
|
|
3193
3396
|
end
|
3194
3397
|
|
3195
3398
|
get '/mounted/fail'
|
3196
|
-
expect(last_response.status).to
|
3399
|
+
expect(last_response.status).to be 202
|
3197
3400
|
expect(last_response.body).to eq('rescued from doh!')
|
3198
3401
|
end
|
3402
|
+
|
3199
3403
|
it 'prefers rescues defined by mounted if they rescue similar error class' do
|
3200
3404
|
subject.rescue_from StandardError do
|
3201
3405
|
rack_response('outer rescue')
|
3202
3406
|
end
|
3203
3407
|
|
3204
|
-
app = Class.new(
|
3408
|
+
app = Class.new(described_class)
|
3205
3409
|
|
3206
3410
|
subject.namespace :mounted do
|
3207
3411
|
rescue_from StandardError do
|
@@ -3214,12 +3418,13 @@ XML
|
|
3214
3418
|
get '/mounted/fail'
|
3215
3419
|
expect(last_response.body).to eq('inner rescue')
|
3216
3420
|
end
|
3421
|
+
|
3217
3422
|
it 'prefers rescues defined by mounted even if outer is more specific' do
|
3218
3423
|
subject.rescue_from ArgumentError do
|
3219
3424
|
rack_response('outer rescue')
|
3220
3425
|
end
|
3221
3426
|
|
3222
|
-
app = Class.new(
|
3427
|
+
app = Class.new(described_class)
|
3223
3428
|
|
3224
3429
|
subject.namespace :mounted do
|
3225
3430
|
rescue_from StandardError do
|
@@ -3232,12 +3437,13 @@ XML
|
|
3232
3437
|
get '/mounted/fail'
|
3233
3438
|
expect(last_response.body).to eq('inner rescue')
|
3234
3439
|
end
|
3440
|
+
|
3235
3441
|
it 'prefers more specific rescues defined by mounted' do
|
3236
3442
|
subject.rescue_from StandardError do
|
3237
3443
|
rack_response('outer rescue')
|
3238
3444
|
end
|
3239
3445
|
|
3240
|
-
app = Class.new(
|
3446
|
+
app = Class.new(described_class)
|
3241
3447
|
|
3242
3448
|
subject.namespace :mounted do
|
3243
3449
|
rescue_from ArgumentError do
|
@@ -3254,19 +3460,19 @@ XML
|
|
3254
3460
|
|
3255
3461
|
it 'collects the routes of the mounted api' do
|
3256
3462
|
subject.namespace :cool do
|
3257
|
-
app = Class.new(Grape::API)
|
3463
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3258
3464
|
app.get('/awesome') {}
|
3259
3465
|
app.post('/sauce') {}
|
3260
3466
|
mount app
|
3261
3467
|
end
|
3262
3468
|
expect(subject.routes.size).to eq(2)
|
3263
|
-
expect(subject.routes.first.path).to match(%r{
|
3264
|
-
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})
|
3265
3471
|
end
|
3266
3472
|
|
3267
3473
|
it 'mounts on a path' do
|
3268
3474
|
subject.namespace :cool do
|
3269
|
-
app = Class.new(Grape::API)
|
3475
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3270
3476
|
app.get '/awesome' do
|
3271
3477
|
'sauce'
|
3272
3478
|
end
|
@@ -3278,12 +3484,12 @@ XML
|
|
3278
3484
|
end
|
3279
3485
|
|
3280
3486
|
it 'mounts on a nested path' do
|
3281
|
-
APP1 = Class.new(
|
3282
|
-
APP2 = Class.new(
|
3487
|
+
APP1 = Class.new(described_class)
|
3488
|
+
APP2 = Class.new(described_class)
|
3283
3489
|
APP2.get '/nice' do
|
3284
3490
|
'play'
|
3285
3491
|
end
|
3286
|
-
#
|
3492
|
+
# NOTE: that the reverse won't work, mount from outside-in
|
3287
3493
|
APP3 = subject
|
3288
3494
|
APP3.mount APP1 => '/app1'
|
3289
3495
|
APP1.mount APP2 => '/app2'
|
@@ -3295,7 +3501,7 @@ XML
|
|
3295
3501
|
end
|
3296
3502
|
|
3297
3503
|
it 'responds to options' do
|
3298
|
-
app = Class.new(
|
3504
|
+
app = Class.new(described_class)
|
3299
3505
|
app.get '/colour' do
|
3300
3506
|
'red'
|
3301
3507
|
end
|
@@ -3309,21 +3515,21 @@ XML
|
|
3309
3515
|
end
|
3310
3516
|
|
3311
3517
|
get '/apples/colour'
|
3312
|
-
expect(last_response.status).to
|
3518
|
+
expect(last_response.status).to be 200
|
3313
3519
|
expect(last_response.body).to eq('red')
|
3314
3520
|
options '/apples/colour'
|
3315
|
-
expect(last_response.status).to
|
3521
|
+
expect(last_response.status).to be 204
|
3316
3522
|
get '/apples/pears/colour'
|
3317
|
-
expect(last_response.status).to
|
3523
|
+
expect(last_response.status).to be 200
|
3318
3524
|
expect(last_response.body).to eq('green')
|
3319
3525
|
options '/apples/pears/colour'
|
3320
|
-
expect(last_response.status).to
|
3526
|
+
expect(last_response.status).to be 204
|
3321
3527
|
end
|
3322
3528
|
|
3323
3529
|
it 'responds to options with path versioning' do
|
3324
3530
|
subject.version 'v1', using: :path
|
3325
3531
|
subject.namespace :apples do
|
3326
|
-
app = Class.new(Grape::API)
|
3532
|
+
app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
|
3327
3533
|
app.get('/colour') do
|
3328
3534
|
'red'
|
3329
3535
|
end
|
@@ -3331,14 +3537,14 @@ XML
|
|
3331
3537
|
end
|
3332
3538
|
|
3333
3539
|
get '/v1/apples/colour'
|
3334
|
-
expect(last_response.status).to
|
3540
|
+
expect(last_response.status).to be 200
|
3335
3541
|
expect(last_response.body).to eq('red')
|
3336
3542
|
options '/v1/apples/colour'
|
3337
|
-
expect(last_response.status).to
|
3543
|
+
expect(last_response.status).to be 204
|
3338
3544
|
end
|
3339
3545
|
|
3340
3546
|
it 'mounts a versioned API with nested resources' do
|
3341
|
-
api = Class.new(
|
3547
|
+
api = Class.new(described_class) do
|
3342
3548
|
version 'v1'
|
3343
3549
|
resources :users do
|
3344
3550
|
get :hello do
|
@@ -3353,7 +3559,7 @@ XML
|
|
3353
3559
|
end
|
3354
3560
|
|
3355
3561
|
it 'mounts a prefixed API with nested resources' do
|
3356
|
-
api = Class.new(
|
3562
|
+
api = Class.new(described_class) do
|
3357
3563
|
prefix 'api'
|
3358
3564
|
resources :users do
|
3359
3565
|
get :hello do
|
@@ -3368,7 +3574,7 @@ XML
|
|
3368
3574
|
end
|
3369
3575
|
|
3370
3576
|
it 'applies format to a mounted API with nested resources' do
|
3371
|
-
api = Class.new(
|
3577
|
+
api = Class.new(described_class) do
|
3372
3578
|
format :json
|
3373
3579
|
resources :users do
|
3374
3580
|
get do
|
@@ -3383,7 +3589,7 @@ XML
|
|
3383
3589
|
end
|
3384
3590
|
|
3385
3591
|
it 'applies auth to a mounted API with nested resources' do
|
3386
|
-
api = Class.new(
|
3592
|
+
api = Class.new(described_class) do
|
3387
3593
|
format :json
|
3388
3594
|
http_basic do |username, password|
|
3389
3595
|
username == 'username' && password == 'password'
|
@@ -3404,7 +3610,7 @@ XML
|
|
3404
3610
|
end
|
3405
3611
|
|
3406
3612
|
it 'mounts multiple versioned APIs with nested resources' do
|
3407
|
-
api1 = Class.new(
|
3613
|
+
api1 = Class.new(described_class) do
|
3408
3614
|
version 'one', using: :header, vendor: 'test'
|
3409
3615
|
resources :users do
|
3410
3616
|
get :hello do
|
@@ -3413,7 +3619,7 @@ XML
|
|
3413
3619
|
end
|
3414
3620
|
end
|
3415
3621
|
|
3416
|
-
api2 = Class.new(
|
3622
|
+
api2 = Class.new(described_class) do
|
3417
3623
|
version 'two', using: :header, vendor: 'test'
|
3418
3624
|
resources :users do
|
3419
3625
|
get :hello do
|
@@ -3432,7 +3638,7 @@ XML
|
|
3432
3638
|
end
|
3433
3639
|
|
3434
3640
|
it 'recognizes potential versions with mounted path' do
|
3435
|
-
a = Class.new(
|
3641
|
+
a = Class.new(described_class) do
|
3436
3642
|
version :v1, using: :path
|
3437
3643
|
|
3438
3644
|
get '/hello' do
|
@@ -3440,7 +3646,7 @@ XML
|
|
3440
3646
|
end
|
3441
3647
|
end
|
3442
3648
|
|
3443
|
-
b = Class.new(
|
3649
|
+
b = Class.new(described_class) do
|
3444
3650
|
version :v1, using: :path
|
3445
3651
|
|
3446
3652
|
get '/world' do
|
@@ -3460,11 +3666,11 @@ XML
|
|
3460
3666
|
|
3461
3667
|
context 'when mounting class extends a subclass of Grape::API' do
|
3462
3668
|
it 'mounts APIs with the same superclass' do
|
3463
|
-
base_api = Class.new(
|
3669
|
+
base_api = Class.new(described_class)
|
3464
3670
|
a = Class.new(base_api)
|
3465
3671
|
b = Class.new(base_api)
|
3466
3672
|
|
3467
|
-
expect { a.mount b }.
|
3673
|
+
expect { a.mount b }.not_to raise_error
|
3468
3674
|
end
|
3469
3675
|
end
|
3470
3676
|
|
@@ -3474,6 +3680,7 @@ XML
|
|
3474
3680
|
def self.included(base)
|
3475
3681
|
base.extend(ClassMethods)
|
3476
3682
|
end
|
3683
|
+
|
3477
3684
|
module ClassMethods
|
3478
3685
|
def my_method
|
3479
3686
|
@test = true
|
@@ -3482,22 +3689,22 @@ XML
|
|
3482
3689
|
end
|
3483
3690
|
end
|
3484
3691
|
|
3485
|
-
it '
|
3692
|
+
it 'correctlies include module in nested mount' do
|
3486
3693
|
module_to_include = included_module
|
3487
|
-
v1 = Class.new(
|
3694
|
+
v1 = Class.new(described_class) do
|
3488
3695
|
version :v1, using: :path
|
3489
3696
|
include module_to_include
|
3490
3697
|
my_method
|
3491
3698
|
end
|
3492
|
-
v2 = Class.new(
|
3699
|
+
v2 = Class.new(described_class) do
|
3493
3700
|
version :v2, using: :path
|
3494
3701
|
end
|
3495
|
-
segment_base = Class.new(
|
3702
|
+
segment_base = Class.new(described_class) do
|
3496
3703
|
mount v1
|
3497
3704
|
mount v2
|
3498
3705
|
end
|
3499
3706
|
|
3500
|
-
Class.new(
|
3707
|
+
Class.new(described_class) do
|
3501
3708
|
mount segment_base
|
3502
3709
|
end
|
3503
3710
|
|
@@ -3532,7 +3739,7 @@ XML
|
|
3532
3739
|
end
|
3533
3740
|
|
3534
3741
|
describe '.endpoint' do
|
3535
|
-
before
|
3742
|
+
before do
|
3536
3743
|
subject.format :json
|
3537
3744
|
subject.get '/endpoint/options' do
|
3538
3745
|
{
|
@@ -3541,6 +3748,7 @@ XML
|
|
3541
3748
|
}
|
3542
3749
|
end
|
3543
3750
|
end
|
3751
|
+
|
3544
3752
|
it 'path' do
|
3545
3753
|
get '/endpoint/options'
|
3546
3754
|
options = ::Grape::Json.load(last_response.body)
|
@@ -3552,7 +3760,7 @@ XML
|
|
3552
3760
|
|
3553
3761
|
describe '.route' do
|
3554
3762
|
context 'plain' do
|
3555
|
-
before
|
3763
|
+
before do
|
3556
3764
|
subject.get '/' do
|
3557
3765
|
route.path
|
3558
3766
|
end
|
@@ -3560,6 +3768,7 @@ XML
|
|
3560
3768
|
route.path
|
3561
3769
|
end
|
3562
3770
|
end
|
3771
|
+
|
3563
3772
|
it 'provides access to route info' do
|
3564
3773
|
get '/'
|
3565
3774
|
expect(last_response.body).to eq('/(.:format)')
|
@@ -3567,8 +3776,9 @@ XML
|
|
3567
3776
|
expect(last_response.body).to eq('/path(.:format)')
|
3568
3777
|
end
|
3569
3778
|
end
|
3779
|
+
|
3570
3780
|
context 'with desc' do
|
3571
|
-
before
|
3781
|
+
before do
|
3572
3782
|
subject.desc 'returns description'
|
3573
3783
|
subject.get '/description' do
|
3574
3784
|
route.description
|
@@ -3578,82 +3788,98 @@ XML
|
|
3578
3788
|
route.params[params[:id]]
|
3579
3789
|
end
|
3580
3790
|
end
|
3791
|
+
|
3581
3792
|
it 'returns route description' do
|
3582
3793
|
get '/description'
|
3583
3794
|
expect(last_response.body).to eq('returns description')
|
3584
3795
|
end
|
3796
|
+
|
3585
3797
|
it 'returns route parameters' do
|
3586
3798
|
get '/params/x'
|
3587
3799
|
expect(last_response.body).to eq('y')
|
3588
3800
|
end
|
3589
3801
|
end
|
3590
3802
|
end
|
3803
|
+
|
3591
3804
|
describe '.format' do
|
3592
3805
|
context ':txt' do
|
3593
|
-
before
|
3806
|
+
before do
|
3594
3807
|
subject.format :txt
|
3595
3808
|
subject.content_type :json, 'application/json'
|
3596
3809
|
subject.get '/meaning_of_life' do
|
3597
3810
|
{ meaning_of_life: 42 }
|
3598
3811
|
end
|
3599
3812
|
end
|
3813
|
+
|
3600
3814
|
it 'forces txt without an extension' do
|
3601
3815
|
get '/meaning_of_life'
|
3602
3816
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3603
3817
|
end
|
3818
|
+
|
3604
3819
|
it 'does not force txt with an extension' do
|
3605
3820
|
get '/meaning_of_life.json'
|
3606
3821
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3607
3822
|
end
|
3823
|
+
|
3608
3824
|
it 'forces txt from a non-accepting header' do
|
3609
3825
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3610
3826
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3611
3827
|
end
|
3612
3828
|
end
|
3829
|
+
|
3613
3830
|
context ':txt only' do
|
3614
|
-
before
|
3831
|
+
before do
|
3615
3832
|
subject.format :txt
|
3616
3833
|
subject.get '/meaning_of_life' do
|
3617
3834
|
{ meaning_of_life: 42 }
|
3618
3835
|
end
|
3619
3836
|
end
|
3837
|
+
|
3620
3838
|
it 'forces txt without an extension' do
|
3621
3839
|
get '/meaning_of_life'
|
3622
3840
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3623
3841
|
end
|
3842
|
+
|
3624
3843
|
it 'accepts specified extension' do
|
3625
3844
|
get '/meaning_of_life.txt'
|
3626
3845
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3627
3846
|
end
|
3847
|
+
|
3628
3848
|
it 'does not accept extensions other than specified' do
|
3629
3849
|
get '/meaning_of_life.json'
|
3630
3850
|
expect(last_response.status).to eq(404)
|
3631
3851
|
end
|
3852
|
+
|
3632
3853
|
it 'forces txt from a non-accepting header' do
|
3633
3854
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
|
3634
3855
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3635
3856
|
end
|
3636
3857
|
end
|
3858
|
+
|
3637
3859
|
context ':json' do
|
3638
|
-
before
|
3860
|
+
before do
|
3639
3861
|
subject.format :json
|
3640
3862
|
subject.content_type :txt, 'text/plain'
|
3641
3863
|
subject.get '/meaning_of_life' do
|
3642
3864
|
{ meaning_of_life: 42 }
|
3643
3865
|
end
|
3644
3866
|
end
|
3867
|
+
|
3645
3868
|
it 'forces json without an extension' do
|
3646
3869
|
get '/meaning_of_life'
|
3647
3870
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3648
3871
|
end
|
3872
|
+
|
3649
3873
|
it 'does not force json with an extension' do
|
3650
3874
|
get '/meaning_of_life.txt'
|
3651
3875
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3652
3876
|
end
|
3877
|
+
|
3653
3878
|
it 'forces json from a non-accepting header' do
|
3654
3879
|
get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
|
3655
3880
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
|
3656
3881
|
end
|
3882
|
+
|
3657
3883
|
it 'can be overwritten with an explicit content type' do
|
3658
3884
|
subject.get '/meaning_of_life_with_content_type' do
|
3659
3885
|
content_type 'text/plain'
|
@@ -3662,6 +3888,7 @@ XML
|
|
3662
3888
|
get '/meaning_of_life_with_content_type'
|
3663
3889
|
expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
|
3664
3890
|
end
|
3891
|
+
|
3665
3892
|
it 'raised :error from middleware' do
|
3666
3893
|
middleware = Class.new(Grape::Middleware::Base) do
|
3667
3894
|
def before
|
@@ -3676,6 +3903,7 @@ XML
|
|
3676
3903
|
expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
|
3677
3904
|
end
|
3678
3905
|
end
|
3906
|
+
|
3679
3907
|
context ':serializable_hash' do
|
3680
3908
|
class SerializableHashExample
|
3681
3909
|
def serializable_hash
|
@@ -3683,9 +3911,10 @@ XML
|
|
3683
3911
|
end
|
3684
3912
|
end
|
3685
3913
|
|
3686
|
-
before
|
3914
|
+
before do
|
3687
3915
|
subject.format :serializable_hash
|
3688
3916
|
end
|
3917
|
+
|
3689
3918
|
it 'instance' do
|
3690
3919
|
subject.get '/example' do
|
3691
3920
|
SerializableHashExample.new
|
@@ -3693,6 +3922,7 @@ XML
|
|
3693
3922
|
get '/example'
|
3694
3923
|
expect(last_response.body).to eq('{"abc":"def"}')
|
3695
3924
|
end
|
3925
|
+
|
3696
3926
|
it 'root' do
|
3697
3927
|
subject.get '/example' do
|
3698
3928
|
{ 'root' => SerializableHashExample.new }
|
@@ -3700,6 +3930,7 @@ XML
|
|
3700
3930
|
get '/example'
|
3701
3931
|
expect(last_response.body).to eq('{"root":{"abc":"def"}}')
|
3702
3932
|
end
|
3933
|
+
|
3703
3934
|
it 'array' do
|
3704
3935
|
subject.get '/examples' do
|
3705
3936
|
[SerializableHashExample.new, SerializableHashExample.new]
|
@@ -3708,23 +3939,26 @@ XML
|
|
3708
3939
|
expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
|
3709
3940
|
end
|
3710
3941
|
end
|
3942
|
+
|
3711
3943
|
context ':xml' do
|
3712
|
-
before
|
3944
|
+
before do
|
3713
3945
|
subject.format :xml
|
3714
3946
|
end
|
3947
|
+
|
3715
3948
|
it 'string' do
|
3716
3949
|
subject.get '/example' do
|
3717
3950
|
'example'
|
3718
3951
|
end
|
3719
3952
|
get '/example'
|
3720
3953
|
expect(last_response.status).to eq(500)
|
3721
|
-
expect(last_response.body).to eq
|
3722
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3723
|
-
<error>
|
3724
|
-
|
3725
|
-
</error>
|
3726
|
-
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
|
3727
3960
|
end
|
3961
|
+
|
3728
3962
|
it 'hash' do
|
3729
3963
|
subject.get '/example' do
|
3730
3964
|
{
|
@@ -3734,28 +3968,30 @@ XML
|
|
3734
3968
|
end
|
3735
3969
|
get '/example'
|
3736
3970
|
expect(last_response.status).to eq(200)
|
3737
|
-
expect(last_response.body).to eq
|
3738
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3739
|
-
<hash>
|
3740
|
-
|
3741
|
-
|
3742
|
-
</hash>
|
3743
|
-
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
|
3744
3978
|
end
|
3979
|
+
|
3745
3980
|
it 'array' do
|
3746
3981
|
subject.get '/example' do
|
3747
3982
|
%w[example1 example2]
|
3748
3983
|
end
|
3749
3984
|
get '/example'
|
3750
3985
|
expect(last_response.status).to eq(200)
|
3751
|
-
expect(last_response.body).to eq
|
3752
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3753
|
-
<strings type="array">
|
3754
|
-
|
3755
|
-
|
3756
|
-
</strings>
|
3757
|
-
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
|
3758
3993
|
end
|
3994
|
+
|
3759
3995
|
it 'raised :error from middleware' do
|
3760
3996
|
middleware = Class.new(Grape::Middleware::Base) do
|
3761
3997
|
def before
|
@@ -3767,12 +4003,12 @@ XML
|
|
3767
4003
|
end
|
3768
4004
|
get '/'
|
3769
4005
|
expect(last_response.status).to eq(42)
|
3770
|
-
expect(last_response.body).to eq
|
3771
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
3772
|
-
<error>
|
3773
|
-
|
3774
|
-
</error>
|
3775
|
-
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
|
3776
4012
|
end
|
3777
4013
|
end
|
3778
4014
|
end
|
@@ -3817,12 +4053,12 @@ XML
|
|
3817
4053
|
|
3818
4054
|
context 'catch-all' do
|
3819
4055
|
before do
|
3820
|
-
api1 = Class.new(
|
4056
|
+
api1 = Class.new(described_class)
|
3821
4057
|
api1.version 'v1', using: :path
|
3822
4058
|
api1.get 'hello' do
|
3823
4059
|
'v1'
|
3824
4060
|
end
|
3825
|
-
api2 = Class.new(
|
4061
|
+
api2 = Class.new(described_class)
|
3826
4062
|
api2.version 'v2', using: :path
|
3827
4063
|
api2.get 'hello' do
|
3828
4064
|
'v2'
|
@@ -3830,6 +4066,7 @@ XML
|
|
3830
4066
|
subject.mount api1
|
3831
4067
|
subject.mount api2
|
3832
4068
|
end
|
4069
|
+
|
3833
4070
|
[true, false].each do |anchor|
|
3834
4071
|
it "anchor=#{anchor}" do
|
3835
4072
|
subject.route :any, '*path', anchor: anchor do
|
@@ -3862,6 +4099,7 @@ XML
|
|
3862
4099
|
expect(last_response.status).to eq(404)
|
3863
4100
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3864
4101
|
end
|
4102
|
+
|
3865
4103
|
it 'does not cascade' do
|
3866
4104
|
subject.version 'v2', using: :path, cascade: false
|
3867
4105
|
get '/v2/hello'
|
@@ -3869,6 +4107,7 @@ XML
|
|
3869
4107
|
expect(last_response.headers.keys).not_to include 'X-Cascade'
|
3870
4108
|
end
|
3871
4109
|
end
|
4110
|
+
|
3872
4111
|
context 'via endpoint' do
|
3873
4112
|
it 'cascades' do
|
3874
4113
|
subject.cascade true
|
@@ -3876,6 +4115,7 @@ XML
|
|
3876
4115
|
expect(last_response.status).to eq(404)
|
3877
4116
|
expect(last_response.headers['X-Cascade']).to eq('pass')
|
3878
4117
|
end
|
4118
|
+
|
3879
4119
|
it 'does not cascade' do
|
3880
4120
|
subject.cascade false
|
3881
4121
|
get '/hello'
|
@@ -3925,12 +4165,14 @@ XML
|
|
3925
4165
|
body false
|
3926
4166
|
end
|
3927
4167
|
end
|
4168
|
+
|
3928
4169
|
it 'returns blank body' do
|
3929
4170
|
get '/blank'
|
3930
4171
|
expect(last_response.status).to eq(204)
|
3931
4172
|
expect(last_response.body).to be_blank
|
3932
4173
|
end
|
3933
4174
|
end
|
4175
|
+
|
3934
4176
|
context 'plain text' do
|
3935
4177
|
before do
|
3936
4178
|
subject.get '/text' do
|
@@ -3939,6 +4181,7 @@ XML
|
|
3939
4181
|
'ignored'
|
3940
4182
|
end
|
3941
4183
|
end
|
4184
|
+
|
3942
4185
|
it 'returns blank body' do
|
3943
4186
|
get '/text'
|
3944
4187
|
expect(last_response.status).to eq(200)
|
@@ -3948,7 +4191,7 @@ XML
|
|
3948
4191
|
end
|
3949
4192
|
|
3950
4193
|
describe 'normal class methods' do
|
3951
|
-
subject(:grape_api) { Class.new(
|
4194
|
+
subject(:grape_api) { Class.new(described_class) }
|
3952
4195
|
|
3953
4196
|
before do
|
3954
4197
|
stub_const('MyAPI', grape_api)
|
@@ -3965,10 +4208,54 @@ XML
|
|
3965
4208
|
end
|
3966
4209
|
end
|
3967
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
|
+
|
3968
4254
|
describe 'const_missing' do
|
3969
|
-
subject(:grape_api) { Class.new(
|
4255
|
+
subject(:grape_api) { Class.new(described_class) }
|
4256
|
+
|
3970
4257
|
let(:mounted) do
|
3971
|
-
Class.new(
|
4258
|
+
Class.new(described_class) do
|
3972
4259
|
get '/missing' do
|
3973
4260
|
SomeRandomConstant
|
3974
4261
|
end
|
@@ -3981,4 +4268,82 @@ XML
|
|
3981
4268
|
expect { get '/const/missing' }.to raise_error(NameError).with_message(/SomeRandomConstant/)
|
3982
4269
|
end
|
3983
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
|
3984
4349
|
end
|