grape 1.5.3 → 1.7.0

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