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.
Files changed (165) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +111 -2
  3. data/CONTRIBUTING.md +2 -1
  4. data/README.md +135 -23
  5. data/UPGRADING.md +237 -46
  6. data/grape.gemspec +5 -5
  7. data/lib/grape/api/instance.rb +34 -42
  8. data/lib/grape/api.rb +21 -16
  9. data/lib/grape/cookies.rb +2 -0
  10. data/lib/grape/dsl/callbacks.rb +1 -1
  11. data/lib/grape/dsl/desc.rb +3 -5
  12. data/lib/grape/dsl/headers.rb +5 -2
  13. data/lib/grape/dsl/helpers.rb +8 -5
  14. data/lib/grape/dsl/inside_route.rb +72 -53
  15. data/lib/grape/dsl/middleware.rb +4 -4
  16. data/lib/grape/dsl/parameters.rb +11 -7
  17. data/lib/grape/dsl/request_response.rb +9 -6
  18. data/lib/grape/dsl/routing.rb +8 -9
  19. data/lib/grape/dsl/settings.rb +5 -5
  20. data/lib/grape/dsl/validations.rb +18 -1
  21. data/lib/grape/eager_load.rb +1 -1
  22. data/lib/grape/endpoint.rb +29 -42
  23. data/lib/grape/error_formatter/json.rb +2 -6
  24. data/lib/grape/error_formatter/xml.rb +2 -6
  25. data/lib/grape/exceptions/empty_message_body.rb +11 -0
  26. data/lib/grape/exceptions/validation.rb +2 -3
  27. data/lib/grape/exceptions/validation_errors.rb +1 -1
  28. data/lib/grape/formatter/json.rb +1 -0
  29. data/lib/grape/formatter/serializable_hash.rb +2 -1
  30. data/lib/grape/formatter/xml.rb +1 -0
  31. data/lib/grape/locale/en.yml +1 -1
  32. data/lib/grape/middleware/auth/base.rb +3 -3
  33. data/lib/grape/middleware/auth/dsl.rb +7 -1
  34. data/lib/grape/middleware/base.rb +6 -3
  35. data/lib/grape/middleware/error.rb +11 -13
  36. data/lib/grape/middleware/formatter.rb +7 -7
  37. data/lib/grape/middleware/stack.rb +10 -3
  38. data/lib/grape/middleware/versioner/accept_version_header.rb +3 -5
  39. data/lib/grape/middleware/versioner/header.rb +6 -4
  40. data/lib/grape/middleware/versioner/param.rb +1 -0
  41. data/lib/grape/middleware/versioner/parse_media_type_patch.rb +2 -1
  42. data/lib/grape/middleware/versioner/path.rb +2 -0
  43. data/lib/grape/parser/json.rb +1 -1
  44. data/lib/grape/parser/xml.rb +1 -1
  45. data/lib/grape/path.rb +1 -0
  46. data/lib/grape/request.rb +4 -1
  47. data/lib/grape/router/attribute_translator.rb +3 -3
  48. data/lib/grape/router/pattern.rb +1 -1
  49. data/lib/grape/router/route.rb +2 -2
  50. data/lib/grape/router.rb +31 -30
  51. data/lib/grape/{serve_file → serve_stream}/file_body.rb +1 -1
  52. data/lib/grape/{serve_file → serve_stream}/sendfile_response.rb +1 -1
  53. data/lib/grape/{serve_file/file_response.rb → serve_stream/stream_response.rb} +8 -8
  54. data/lib/grape/util/base_inheritable.rb +2 -2
  55. data/lib/grape/util/inheritable_setting.rb +1 -3
  56. data/lib/grape/util/lazy_value.rb +4 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_iterator.rb +8 -0
  59. data/lib/grape/validations/multiple_attributes_iterator.rb +1 -1
  60. data/lib/grape/validations/params_scope.rb +97 -62
  61. data/lib/grape/validations/single_attribute_iterator.rb +1 -1
  62. data/lib/grape/validations/types/custom_type_coercer.rb +16 -3
  63. data/lib/grape/validations/types/dry_type_coercer.rb +1 -1
  64. data/lib/grape/validations/types/invalid_value.rb +24 -0
  65. data/lib/grape/validations/types/json.rb +2 -1
  66. data/lib/grape/validations/types/primitive_coercer.rb +4 -5
  67. data/lib/grape/validations/types.rb +1 -4
  68. data/lib/grape/validations/validator_factory.rb +1 -1
  69. data/lib/grape/validations/validators/all_or_none.rb +8 -5
  70. data/lib/grape/validations/validators/allow_blank.rb +9 -7
  71. data/lib/grape/validations/validators/as.rb +6 -8
  72. data/lib/grape/validations/validators/at_least_one_of.rb +7 -4
  73. data/lib/grape/validations/validators/base.rb +74 -69
  74. data/lib/grape/validations/validators/coerce.rb +63 -76
  75. data/lib/grape/validations/validators/default.rb +36 -34
  76. data/lib/grape/validations/validators/exactly_one_of.rb +9 -6
  77. data/lib/grape/validations/validators/except_values.rb +13 -11
  78. data/lib/grape/validations/validators/multiple_params_base.rb +24 -19
  79. data/lib/grape/validations/validators/mutual_exclusion.rb +8 -5
  80. data/lib/grape/validations/validators/presence.rb +7 -4
  81. data/lib/grape/validations/validators/regexp.rb +8 -5
  82. data/lib/grape/validations/validators/same_as.rb +18 -15
  83. data/lib/grape/validations/validators/values.rb +61 -56
  84. data/lib/grape/validations.rb +6 -0
  85. data/lib/grape/version.rb +1 -1
  86. data/lib/grape.rb +7 -3
  87. data/spec/grape/api/custom_validations_spec.rb +77 -45
  88. data/spec/grape/api/deeply_included_options_spec.rb +3 -3
  89. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -1
  90. data/spec/grape/api/invalid_format_spec.rb +2 -0
  91. data/spec/grape/api/recognize_path_spec.rb +1 -1
  92. data/spec/grape/api/routes_with_requirements_spec.rb +8 -8
  93. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -15
  94. data/spec/grape/api_remount_spec.rb +25 -19
  95. data/spec/grape/api_spec.rb +576 -211
  96. data/spec/grape/dsl/callbacks_spec.rb +2 -1
  97. data/spec/grape/dsl/headers_spec.rb +39 -9
  98. data/spec/grape/dsl/helpers_spec.rb +3 -2
  99. data/spec/grape/dsl/inside_route_spec.rb +185 -34
  100. data/spec/grape/dsl/logger_spec.rb +16 -18
  101. data/spec/grape/dsl/middleware_spec.rb +2 -1
  102. data/spec/grape/dsl/parameters_spec.rb +2 -0
  103. data/spec/grape/dsl/request_response_spec.rb +1 -0
  104. data/spec/grape/dsl/routing_spec.rb +10 -7
  105. data/spec/grape/endpoint/declared_spec.rb +848 -0
  106. data/spec/grape/endpoint_spec.rb +77 -589
  107. data/spec/grape/entity_spec.rb +29 -23
  108. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -0
  109. data/spec/grape/exceptions/invalid_accept_header_spec.rb +61 -22
  110. data/spec/grape/exceptions/validation_errors_spec.rb +13 -10
  111. data/spec/grape/exceptions/validation_spec.rb +5 -3
  112. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -7
  113. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -8
  114. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -8
  115. data/spec/grape/integration/rack_sendfile_spec.rb +13 -9
  116. data/spec/grape/loading_spec.rb +8 -8
  117. data/spec/grape/middleware/auth/dsl_spec.rb +15 -6
  118. data/spec/grape/middleware/auth/strategies_spec.rb +61 -21
  119. data/spec/grape/middleware/base_spec.rb +24 -15
  120. data/spec/grape/middleware/error_spec.rb +3 -3
  121. data/spec/grape/middleware/exception_spec.rb +111 -161
  122. data/spec/grape/middleware/formatter_spec.rb +28 -7
  123. data/spec/grape/middleware/globals_spec.rb +7 -4
  124. data/spec/grape/middleware/stack_spec.rb +15 -12
  125. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -1
  126. data/spec/grape/middleware/versioner/header_spec.rb +14 -13
  127. data/spec/grape/middleware/versioner/param_spec.rb +7 -1
  128. data/spec/grape/middleware/versioner/path_spec.rb +5 -1
  129. data/spec/grape/middleware/versioner_spec.rb +1 -1
  130. data/spec/grape/parser_spec.rb +4 -0
  131. data/spec/grape/path_spec.rb +52 -52
  132. data/spec/grape/presenters/presenter_spec.rb +7 -6
  133. data/spec/grape/request_spec.rb +6 -4
  134. data/spec/grape/util/inheritable_setting_spec.rb +7 -7
  135. data/spec/grape/util/inheritable_values_spec.rb +3 -2
  136. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -1
  137. data/spec/grape/util/stackable_values_spec.rb +7 -5
  138. data/spec/grape/validations/instance_behaivour_spec.rb +9 -10
  139. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +14 -3
  140. data/spec/grape/validations/params_scope_spec.rb +72 -10
  141. data/spec/grape/validations/single_attribute_iterator_spec.rb +18 -6
  142. data/spec/grape/validations/types/primitive_coercer_spec.rb +63 -7
  143. data/spec/grape/validations/types_spec.rb +8 -8
  144. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -56
  145. data/spec/grape/validations/validators/allow_blank_spec.rb +136 -140
  146. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -56
  147. data/spec/grape/validations/validators/coerce_spec.rb +248 -33
  148. data/spec/grape/validations/validators/default_spec.rb +121 -78
  149. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -77
  150. data/spec/grape/validations/validators/except_values_spec.rb +4 -3
  151. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -77
  152. data/spec/grape/validations/validators/presence_spec.rb +16 -1
  153. data/spec/grape/validations/validators/regexp_spec.rb +25 -31
  154. data/spec/grape/validations/validators/same_as_spec.rb +14 -20
  155. data/spec/grape/validations/validators/values_spec.rb +183 -178
  156. data/spec/grape/validations_spec.rb +342 -29
  157. data/spec/integration/eager_load/eager_load_spec.rb +15 -0
  158. data/spec/integration/multi_json/json_spec.rb +1 -1
  159. data/spec/integration/multi_xml/xml_spec.rb +1 -1
  160. data/spec/shared/versioning_examples.rb +32 -29
  161. data/spec/spec_helper.rb +12 -12
  162. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  163. data/spec/support/chunks.rb +14 -0
  164. data/spec/support/versioned_helpers.rb +4 -6
  165. metadata +110 -102
@@ -4,7 +4,10 @@ require 'spec_helper'
4
4
  require 'shared/versioning_examples'
5
5
 
6
6
  describe Grape::API do
7
- subject { Class.new(Grape::API) }
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 eql 200
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 eql 404
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 eql 204
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 eql 405
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
- it_should_behave_like 'versioning' do
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
- it_should_behave_like 'versioning' do
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
- it_should_behave_like 'versioning' do
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
- it_should_behave_like 'versioning' do
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(:each) do
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 405
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 eql 201
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 eql 405
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 eql 405
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 eql 405
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 eql 200
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 eql 405
653
- expect(last_response.body).to eq <<-XML
654
- <?xml version="1.0" encoding="UTF-8"?>
655
- <error>
656
- <message>405 Not Allowed</message>
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 eql 405
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 eql 204
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 eql 204
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 eql 405
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 eql 200
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 eql 400
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 :each do
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 eql 204
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 eql 405
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 :each do
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 eql 405
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 eql 405
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') { nil }
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(Grape::API) do
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!).exactly(2).times
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!).exactly(1).times
1008
- expect(b).to receive(:do_something!).exactly(1).times
1009
- expect(c).to receive(:do_something!).exactly(1).times
1010
- expect(d).to receive(:do_something!).exactly(1).times
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 eql 200
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!).exactly(1).times
1037
- expect(b).to receive(:do_something!).exactly(1).times
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 eql 400
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).exactly(1).times
1067
- expect(b).to receive(:here).with(2).exactly(1).times
1068
- expect(c).to receive(:here).with(3).exactly(1).times
1069
- expect(d).to receive(:here).with(4).exactly(1).times
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 eql 200
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 eql 500
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 eql 500
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 eql 401
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 eql 200
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 eql 200
1577
+ expect(last_response.status).to be 200
1442
1578
  get '/admin/hello'
1443
- expect(last_response.status).to eql 401
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 eql 401
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 eql 200
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 eql 200
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 eql 401
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 eql 200
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
- it 'allows setting a custom logger' do
1519
- mylogger = Class.new
1520
- subject.logger mylogger
1521
- expect(mylogger).to receive(:info).exactly(1).times
1522
- subject.logger.info 'this will be logged'
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 'defaults to a standard logger log format' do
1526
- t = Time.at(100)
1527
- allow(Time).to receive(:now).and_return(t)
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 eql 404
1790
+ expect(last_response.status).to be 404
1658
1791
  get '/legacy/abc'
1659
- expect(last_response.status).to eql 200
1792
+ expect(last_response.status).to be 200
1660
1793
  get '/legacy/def'
1661
- expect(last_response.status).to eql 404
1794
+ expect(last_response.status).to be 404
1662
1795
  get '/new/def'
1663
- expect(last_response.status).to eql 200
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(Grape::API) }
1884
- let(:b) { Class.new(Grape::API) }
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 eql 500
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 eql 500
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 eql 500
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 eql 402
2092
+ expect(last_response.status).to be 402
1960
2093
 
1961
2094
  get '/standard_error'
1962
- expect(last_response.status).to eql 401
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 eql 500
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 eql 500
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 eql 202
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 eql 500
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 eql 500
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 eql 500
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 eql 500
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 eql 500
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, 'undefined method `not_exist_method\'')
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 eql 500
2350
+ expect(last_response.status).to be 500
2211
2351
  get '/caught_parent'
2212
- expect(last_response.status).to eql 500
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 eql 500
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 :each do
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 :each do
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 :each do
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 :each do
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 :each do
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 :each do
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 :each do
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 :each do
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 :each do
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 eql 200
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 eql 500
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 eql 400
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 eql 408
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 eql 408
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(:each) do
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 :each do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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' => { required: true, type: 'Array' },
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' => { required: true, type: 'Array' },
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(Grape::API)
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 eql 202
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(Grape::API)
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(Grape::API)
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(Grape::API)
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{\/cool\/awesome})
3264
- expect(subject.routes.last.path).to match(%r{\/cool\/sauce})
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(Grape::API)
3282
- APP2 = Class.new(Grape::API)
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
- # note that the reverse won't work, mount from outside-in
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(Grape::API)
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 eql 200
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 eql 204
3521
+ expect(last_response.status).to be 204
3316
3522
  get '/apples/pears/colour'
3317
- expect(last_response.status).to eql 200
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 eql 204
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 eql 200
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 eql 204
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API) do
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(Grape::API)
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 }.to_not raise_error
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 'should correctly include module in nested mount' do
3692
+ it 'correctlies include module in nested mount' do
3486
3693
  module_to_include = included_module
3487
- v1 = Class.new(Grape::API) do
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(Grape::API) do
3699
+ v2 = Class.new(described_class) do
3493
3700
  version :v2, using: :path
3494
3701
  end
3495
- segment_base = Class.new(Grape::API) do
3702
+ segment_base = Class.new(described_class) do
3496
3703
  mount v1
3497
3704
  mount v2
3498
3705
  end
3499
3706
 
3500
- Class.new(Grape::API) do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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(:each) do
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 <<-XML
3722
- <?xml version="1.0" encoding="UTF-8"?>
3723
- <error>
3724
- <message>cannot convert String to xml</message>
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 <<-XML
3738
- <?xml version="1.0" encoding="UTF-8"?>
3739
- <hash>
3740
- <example1>example1</example1>
3741
- <example2>example2</example2>
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 <<-XML
3752
- <?xml version="1.0" encoding="UTF-8"?>
3753
- <strings type="array">
3754
- <string>example1</string>
3755
- <string>example2</string>
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 <<-XML
3771
- <?xml version="1.0" encoding="UTF-8"?>
3772
- <error>
3773
- <message>Unauthorized</message>
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(Grape::API)
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(Grape::API)
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(Grape::API) }
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(Grape::API) }
4255
+ subject(:grape_api) { Class.new(described_class) }
4256
+
3970
4257
  let(:mounted) do
3971
- Class.new(Grape::API) do
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