grape 1.5.3 → 1.7.1

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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +92 -0
  3. data/CONTRIBUTING.md +32 -1
  4. data/README.md +176 -25
  5. data/UPGRADING.md +61 -4
  6. data/grape.gemspec +6 -6
  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 +4 -20
  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 +13 -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 +22 -37
  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 +3 -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 +3 -3
  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 +4 -1
  48. data/lib/grape/router/attribute_translator.rb +1 -1
  49. data/lib/grape/router/pattern.rb +1 -1
  50. data/lib/grape/router/route.rb +2 -2
  51. data/lib/grape/router.rb +6 -0
  52. data/lib/grape/types/invalid_value.rb +8 -0
  53. data/lib/grape/util/cache.rb +1 -1
  54. data/lib/grape/util/inheritable_setting.rb +1 -3
  55. data/lib/grape/util/json.rb +2 -0
  56. data/lib/grape/util/lazy_value.rb +3 -2
  57. data/lib/grape/util/strict_hash_configuration.rb +1 -1
  58. data/lib/grape/validations/attributes_doc.rb +58 -0
  59. data/lib/grape/validations/params_scope.rb +138 -79
  60. data/lib/grape/validations/types/array_coercer.rb +0 -2
  61. data/lib/grape/validations/types/custom_type_coercer.rb +1 -0
  62. data/lib/grape/validations/types/dry_type_coercer.rb +4 -8
  63. data/lib/grape/validations/types/invalid_value.rb +0 -7
  64. data/lib/grape/validations/types/json.rb +2 -1
  65. data/lib/grape/validations/types/primitive_coercer.rb +16 -8
  66. data/lib/grape/validations/types/set_coercer.rb +0 -2
  67. data/lib/grape/validations/types.rb +98 -30
  68. data/lib/grape/validations/validators/all_or_none_of_validator.rb +16 -0
  69. data/lib/grape/validations/validators/allow_blank_validator.rb +20 -0
  70. data/lib/grape/validations/validators/as_validator.rb +14 -0
  71. data/lib/grape/validations/validators/at_least_one_of_validator.rb +15 -0
  72. data/lib/grape/validations/validators/base.rb +82 -70
  73. data/lib/grape/validations/validators/coerce_validator.rb +75 -0
  74. data/lib/grape/validations/validators/default_validator.rb +51 -0
  75. data/lib/grape/validations/validators/exactly_one_of_validator.rb +17 -0
  76. data/lib/grape/validations/validators/except_values_validator.rb +24 -0
  77. data/lib/grape/validations/validators/multiple_params_base.rb +24 -20
  78. data/lib/grape/validations/validators/mutual_exclusion_validator.rb +16 -0
  79. data/lib/grape/validations/validators/presence_validator.rb +15 -0
  80. data/lib/grape/validations/validators/regexp_validator.rb +16 -0
  81. data/lib/grape/validations/validators/same_as_validator.rb +29 -0
  82. data/lib/grape/validations/validators/values_validator.rb +88 -0
  83. data/lib/grape/validations.rb +16 -6
  84. data/lib/grape/version.rb +1 -1
  85. data/lib/grape.rb +77 -29
  86. data/spec/grape/api/custom_validations_spec.rb +116 -45
  87. data/spec/grape/api/deeply_included_options_spec.rb +3 -5
  88. data/spec/grape/api/defines_boolean_in_params_spec.rb +2 -3
  89. data/spec/grape/api/documentation_spec.rb +59 -0
  90. data/spec/grape/api/inherited_helpers_spec.rb +0 -2
  91. data/spec/grape/api/instance_spec.rb +0 -1
  92. data/spec/grape/api/invalid_format_spec.rb +2 -2
  93. data/spec/grape/api/namespace_parameters_in_route_spec.rb +0 -2
  94. data/spec/grape/api/nested_helpers_spec.rb +0 -2
  95. data/spec/grape/api/optional_parameters_in_route_spec.rb +0 -2
  96. data/spec/grape/api/parameters_modification_spec.rb +0 -2
  97. data/spec/grape/api/patch_method_helpers_spec.rb +0 -2
  98. data/spec/grape/api/recognize_path_spec.rb +1 -3
  99. data/spec/grape/api/required_parameters_in_route_spec.rb +0 -2
  100. data/spec/grape/api/required_parameters_with_invalid_method_spec.rb +0 -2
  101. data/spec/grape/api/routes_with_requirements_spec.rb +8 -10
  102. data/spec/grape/api/shared_helpers_exactly_one_of_spec.rb +9 -17
  103. data/spec/grape/api/shared_helpers_spec.rb +0 -2
  104. data/spec/grape/api_remount_spec.rb +16 -16
  105. data/spec/grape/api_spec.rb +462 -251
  106. data/spec/grape/config_spec.rb +0 -2
  107. data/spec/grape/dsl/callbacks_spec.rb +2 -3
  108. data/spec/grape/dsl/desc_spec.rb +2 -2
  109. data/spec/grape/dsl/headers_spec.rb +39 -11
  110. data/spec/grape/dsl/helpers_spec.rb +3 -4
  111. data/spec/grape/dsl/inside_route_spec.rb +16 -16
  112. data/spec/grape/dsl/logger_spec.rb +15 -19
  113. data/spec/grape/dsl/middleware_spec.rb +2 -3
  114. data/spec/grape/dsl/parameters_spec.rb +2 -2
  115. data/spec/grape/dsl/request_response_spec.rb +7 -8
  116. data/spec/grape/dsl/routing_spec.rb +11 -10
  117. data/spec/grape/dsl/settings_spec.rb +0 -2
  118. data/spec/grape/dsl/validations_spec.rb +0 -17
  119. data/spec/grape/endpoint/declared_spec.rb +261 -16
  120. data/spec/grape/endpoint_spec.rb +88 -59
  121. data/spec/grape/entity_spec.rb +22 -23
  122. data/spec/grape/exceptions/base_spec.rb +16 -2
  123. data/spec/grape/exceptions/body_parse_errors_spec.rb +3 -2
  124. data/spec/grape/exceptions/invalid_accept_header_spec.rb +64 -24
  125. data/spec/grape/exceptions/invalid_formatter_spec.rb +0 -2
  126. data/spec/grape/exceptions/invalid_response_spec.rb +0 -2
  127. data/spec/grape/exceptions/invalid_versioner_option_spec.rb +1 -3
  128. data/spec/grape/exceptions/missing_group_type_spec.rb +21 -0
  129. data/spec/grape/exceptions/missing_mime_type_spec.rb +0 -2
  130. data/spec/grape/exceptions/missing_option_spec.rb +1 -3
  131. data/spec/grape/exceptions/unknown_options_spec.rb +0 -2
  132. data/spec/grape/exceptions/unknown_validator_spec.rb +0 -2
  133. data/spec/grape/exceptions/unsupported_group_type_spec.rb +23 -0
  134. data/spec/grape/exceptions/validation_errors_spec.rb +13 -11
  135. data/spec/grape/exceptions/validation_spec.rb +5 -5
  136. data/spec/grape/extensions/param_builders/hash_spec.rb +7 -9
  137. data/spec/grape/extensions/param_builders/hash_with_indifferent_access_spec.rb +8 -10
  138. data/spec/grape/extensions/param_builders/hashie/mash_spec.rb +8 -10
  139. data/spec/grape/integration/global_namespace_function_spec.rb +0 -2
  140. data/spec/grape/integration/rack_sendfile_spec.rb +1 -3
  141. data/spec/grape/integration/rack_spec.rb +6 -7
  142. data/spec/grape/loading_spec.rb +8 -10
  143. data/spec/grape/middleware/auth/base_spec.rb +0 -1
  144. data/spec/grape/middleware/auth/dsl_spec.rb +15 -8
  145. data/spec/grape/middleware/auth/strategies_spec.rb +60 -22
  146. data/spec/grape/middleware/base_spec.rb +28 -19
  147. data/spec/grape/middleware/error_spec.rb +8 -3
  148. data/spec/grape/middleware/exception_spec.rb +111 -163
  149. data/spec/grape/middleware/formatter_spec.rb +33 -14
  150. data/spec/grape/middleware/globals_spec.rb +7 -6
  151. data/spec/grape/middleware/stack_spec.rb +14 -14
  152. data/spec/grape/middleware/versioner/accept_version_header_spec.rb +2 -3
  153. data/spec/grape/middleware/versioner/header_spec.rb +30 -15
  154. data/spec/grape/middleware/versioner/param_spec.rb +7 -3
  155. data/spec/grape/middleware/versioner/path_spec.rb +5 -3
  156. data/spec/grape/middleware/versioner_spec.rb +1 -3
  157. data/spec/grape/named_api_spec.rb +0 -2
  158. data/spec/grape/parser_spec.rb +4 -2
  159. data/spec/grape/path_spec.rb +52 -54
  160. data/spec/grape/presenters/presenter_spec.rb +7 -8
  161. data/spec/grape/request_spec.rb +6 -6
  162. data/spec/grape/util/inheritable_setting_spec.rb +7 -8
  163. data/spec/grape/util/inheritable_values_spec.rb +3 -3
  164. data/spec/grape/util/reverse_stackable_values_spec.rb +3 -2
  165. data/spec/grape/util/stackable_values_spec.rb +7 -6
  166. data/spec/grape/util/strict_hash_configuration_spec.rb +0 -1
  167. data/spec/grape/validations/attributes_doc_spec.rb +153 -0
  168. data/spec/grape/validations/instance_behaivour_spec.rb +9 -12
  169. data/spec/grape/validations/multiple_attributes_iterator_spec.rb +1 -2
  170. data/spec/grape/validations/params_scope_spec.rb +361 -96
  171. data/spec/grape/validations/single_attribute_iterator_spec.rb +2 -3
  172. data/spec/grape/validations/types/array_coercer_spec.rb +0 -2
  173. data/spec/grape/validations/types/primitive_coercer_spec.rb +24 -9
  174. data/spec/grape/validations/types/set_coercer_spec.rb +0 -2
  175. data/spec/grape/validations/types_spec.rb +36 -10
  176. data/spec/grape/validations/validators/all_or_none_spec.rb +50 -58
  177. data/spec/grape/validations/validators/allow_blank_spec.rb +135 -141
  178. data/spec/grape/validations/validators/at_least_one_of_spec.rb +50 -58
  179. data/spec/grape/validations/validators/coerce_spec.rb +23 -24
  180. data/spec/grape/validations/validators/default_spec.rb +72 -80
  181. data/spec/grape/validations/validators/exactly_one_of_spec.rb +71 -79
  182. data/spec/grape/validations/validators/except_values_spec.rb +3 -5
  183. data/spec/grape/validations/validators/mutual_exclusion_spec.rb +71 -79
  184. data/spec/grape/validations/validators/presence_spec.rb +16 -3
  185. data/spec/grape/validations/validators/regexp_spec.rb +25 -33
  186. data/spec/grape/validations/validators/same_as_spec.rb +14 -22
  187. data/spec/grape/validations/validators/values_spec.rb +201 -179
  188. data/spec/grape/validations_spec.rb +171 -79
  189. data/spec/integration/eager_load/eager_load_spec.rb +2 -2
  190. data/spec/integration/multi_json/json_spec.rb +1 -3
  191. data/spec/integration/multi_xml/xml_spec.rb +1 -3
  192. data/spec/shared/versioning_examples.rb +12 -9
  193. data/spec/spec_helper.rb +21 -6
  194. data/spec/support/basic_auth_encode_helpers.rb +1 -1
  195. metadata +41 -29
  196. data/lib/grape/validations/validators/all_or_none.rb +0 -15
  197. data/lib/grape/validations/validators/allow_blank.rb +0 -18
  198. data/lib/grape/validations/validators/as.rb +0 -16
  199. data/lib/grape/validations/validators/at_least_one_of.rb +0 -14
  200. data/lib/grape/validations/validators/coerce.rb +0 -91
  201. data/lib/grape/validations/validators/default.rb +0 -48
  202. data/lib/grape/validations/validators/exactly_one_of.rb +0 -16
  203. data/lib/grape/validations/validators/except_values.rb +0 -22
  204. data/lib/grape/validations/validators/mutual_exclusion.rb +0 -15
  205. data/lib/grape/validations/validators/presence.rb +0 -12
  206. data/lib/grape/validations/validators/regexp.rb +0 -13
  207. data/lib/grape/validations/validators/same_as.rb +0 -26
  208. data/lib/grape/validations/validators/values.rb +0 -83
  209. data/spec/grape/dsl/configuration_spec.rb +0 -16
  210. data/spec/grape/validations/attributes_iterator_spec.rb +0 -6
  211. 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,
@@ -101,26 +103,10 @@ describe Grape::API do
101
103
  }
102
104
  end
103
105
  end
104
-
105
- # Behavior as defined by rfc2616 when no header is defined
106
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
107
- describe 'no specified accept header' do
108
- # subject.version 'v1', using: :header
109
- # subject.get '/hello' do
110
- # 'hello'
111
- # end
112
-
113
- # it 'routes' do
114
- # get '/hello'
115
- # last_response.status.should eql 200
116
- # end
117
- end
118
-
119
- # pending 'routes if any media type is allowed'
120
106
  end
121
107
 
122
108
  describe '.version using accept_version_header' do
123
- it_should_behave_like 'versioning' do
109
+ it_behaves_like 'versioning' do
124
110
  let(:macro_options) do
125
111
  {
126
112
  using: :accept_version_header
@@ -389,7 +375,7 @@ describe Grape::API do
389
375
  end
390
376
  end
391
377
 
392
- before(:each) do
378
+ before do
393
379
  allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_json).and_return('abc')
394
380
  allow_any_instance_of(ApiSpec::DummyFormatClass).to receive(:to_txt).and_return('def')
395
381
 
@@ -446,9 +432,10 @@ describe Grape::API do
446
432
  expect(last_response.body).to eql 'hiya'
447
433
  end
448
434
 
435
+ objects = ['string', :symbol, 1, -1.1, {}, [], true, false, nil].freeze
449
436
  %i[put post].each do |verb|
450
- context verb do
451
- ['string', :symbol, 1, -1.1, {}, [], true, false, nil].each do |object|
437
+ context verb.to_s do
438
+ objects.each do |object|
452
439
  it "allows a(n) #{object.class} json object in params" do
453
440
  subject.format :json
454
441
  subject.send(verb) do
@@ -459,6 +446,7 @@ describe Grape::API do
459
446
  expect(last_response.body).to eql ::Grape::Json.dump(object)
460
447
  expect(last_request.params).to eql({})
461
448
  end
449
+
462
450
  it 'stores input in api.request.input' do
463
451
  subject.format :json
464
452
  subject.send(verb) do
@@ -468,6 +456,7 @@ describe Grape::API do
468
456
  expect(last_response.status).to eq(verb == :post ? 201 : 200)
469
457
  expect(last_response.body).to eql ::Grape::Json.dump(object).to_json
470
458
  end
459
+
471
460
  context 'chunked transfer encoding' do
472
461
  it 'stores input in api.request.input' do
473
462
  subject.format :json
@@ -562,7 +551,8 @@ describe Grape::API do
562
551
  send(other_verb, '/example')
563
552
  expected_rc = if other_verb == 'options' then 204
564
553
  elsif other_verb == 'head' && verb == 'get' then 200
565
- else 405
554
+ else
555
+ 405
566
556
  end
567
557
  expect(last_response.status).to eql expected_rc
568
558
  end
@@ -575,7 +565,7 @@ describe Grape::API do
575
565
  end
576
566
 
577
567
  post '/example'
578
- expect(last_response.status).to eql 201
568
+ expect(last_response.status).to be 201
579
569
  expect(last_response.body).to eql 'Created'
580
570
  end
581
571
 
@@ -585,7 +575,7 @@ describe Grape::API do
585
575
  'example'
586
576
  end
587
577
  put '/example'
588
- expect(last_response.status).to eql 405
578
+ expect(last_response.status).to be 405
589
579
  expect(last_response.body).to eql '405 Not Allowed'
590
580
  expect(last_response.headers['X-Custom-Header']).to eql 'foo'
591
581
  end
@@ -593,15 +583,17 @@ describe Grape::API do
593
583
  it 'runs only the before filter on 405 bad method' do
594
584
  subject.namespace :example do
595
585
  before { header 'X-Custom-Header', 'foo' }
586
+
596
587
  before_validation { raise 'before_validation filter should not run' }
597
588
  after_validation { raise 'after_validation filter should not run' }
598
589
  after { raise 'after filter should not run' }
590
+
599
591
  params { requires :only_for_get }
600
592
  get
601
593
  end
602
594
 
603
595
  post '/example'
604
- expect(last_response.status).to eql 405
596
+ expect(last_response.status).to be 405
605
597
  expect(last_response.headers['X-Custom-Header']).to eql 'foo'
606
598
  end
607
599
 
@@ -610,29 +602,33 @@ describe Grape::API do
610
602
  subject.namespace :example do
611
603
  before do
612
604
  raise 'before filter ran twice' if already_run
605
+
613
606
  already_run = true
614
607
  header 'X-Custom-Header', 'foo'
615
608
  end
609
+
616
610
  get
617
611
  end
618
612
 
619
613
  post '/example'
620
- expect(last_response.status).to eql 405
614
+ expect(last_response.status).to be 405
621
615
  expect(last_response.headers['X-Custom-Header']).to eql 'foo'
622
616
  end
623
617
 
624
618
  it 'runs all filters and body with a custom OPTIONS method' do
625
619
  subject.namespace :example do
626
620
  before { header 'X-Custom-Header-1', 'foo' }
621
+
627
622
  before_validation { header 'X-Custom-Header-2', 'foo' }
628
623
  after_validation { header 'X-Custom-Header-3', 'foo' }
629
624
  after { header 'X-Custom-Header-4', 'foo' }
625
+
630
626
  options { 'yup' }
631
627
  get
632
628
  end
633
629
 
634
630
  options '/example'
635
- expect(last_response.status).to eql 200
631
+ expect(last_response.status).to be 200
636
632
  expect(last_response.body).to eql 'yup'
637
633
  expect(last_response.headers['Allow']).to be_nil
638
634
  expect(last_response.headers['X-Custom-Header-1']).to eql 'foo'
@@ -649,13 +645,13 @@ describe Grape::API do
649
645
  end
650
646
 
651
647
  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
648
+ expect(last_response.status).to be 405
649
+ expect(last_response.body).to eq <<~XML
650
+ <?xml version="1.0" encoding="UTF-8"?>
651
+ <error>
652
+ <message>405 Not Allowed</message>
653
+ </error>
654
+ XML
659
655
  end
660
656
  end
661
657
 
@@ -669,7 +665,7 @@ XML
669
665
  'example'
670
666
  end
671
667
  put '/example'
672
- expect(last_response.status).to eql 405
668
+ expect(last_response.status).to be 405
673
669
  expect(last_response.body).to eql '405 Not Allowed'
674
670
  end
675
671
  end
@@ -713,7 +709,7 @@ XML
713
709
  end
714
710
 
715
711
  it 'returns a 204' do
716
- expect(last_response.status).to eql 204
712
+ expect(last_response.status).to be 204
717
713
  end
718
714
 
719
715
  it 'has an empty body' do
@@ -749,11 +745,53 @@ XML
749
745
  end
750
746
  end
751
747
 
748
+ describe 'when a resource routes by POST, GET, PATCH, PUT, and DELETE' do
749
+ before do
750
+ subject.namespace :example do
751
+ get do
752
+ 'example'
753
+ end
754
+
755
+ patch do
756
+ 'example'
757
+ end
758
+
759
+ post do
760
+ 'example'
761
+ end
762
+
763
+ delete do
764
+ 'example'
765
+ end
766
+
767
+ put do
768
+ 'example'
769
+ end
770
+ end
771
+ options '/example'
772
+ end
773
+
774
+ describe 'it adds an OPTIONS route for namespaced endpoints that' do
775
+ it 'returns a 204' do
776
+ expect(last_response.status).to be 204
777
+ end
778
+
779
+ it 'has an empty body' do
780
+ expect(last_response.body).to be_blank
781
+ end
782
+
783
+ it 'has an Allow header' do
784
+ expect(last_response.headers['Allow']).to eql 'OPTIONS, GET, PATCH, POST, DELETE, PUT, HEAD'
785
+ end
786
+ end
787
+ end
788
+
752
789
  describe 'adds an OPTIONS route for namespaced endpoints that' do
753
790
  before do
754
791
  subject.before { header 'X-Custom-Header', 'foo' }
755
792
  subject.namespace :example do
756
793
  before { header 'X-Custom-Header-2', 'foo' }
794
+
757
795
  get :inner do
758
796
  'example/inner'
759
797
  end
@@ -762,7 +800,7 @@ XML
762
800
  end
763
801
 
764
802
  it 'returns a 204' do
765
- expect(last_response.status).to eql 204
803
+ expect(last_response.status).to be 204
766
804
  end
767
805
 
768
806
  it 'has an empty body' do
@@ -800,7 +838,7 @@ XML
800
838
  end
801
839
 
802
840
  it 'returns a 405' do
803
- expect(last_response.status).to eql 405
841
+ expect(last_response.status).to be 405
804
842
  end
805
843
 
806
844
  it 'contains error message in body' do
@@ -816,7 +854,7 @@ XML
816
854
  end
817
855
  end
818
856
 
819
- describe 'when hook behaviour is controlled by attributes on the route ' do
857
+ describe 'when hook behaviour is controlled by attributes on the route' do
820
858
  before do
821
859
  subject.before do
822
860
  error!('Access Denied', 401) unless route.options[:secret] == params[:secret]
@@ -839,28 +877,31 @@ XML
839
877
  let(:response) { delete('/example') }
840
878
 
841
879
  it 'responds with a 405 status' do
842
- expect(response.status).to eql 405
880
+ expect(response.status).to be 405
843
881
  end
844
882
  end
845
883
 
846
884
  context 'when HTTP method is defined with attribute' do
847
885
  let(:response) { post('/example?secret=incorrect_password') }
886
+
848
887
  it 'responds with the defined error in the before hook' do
849
- expect(response.status).to eql 401
888
+ expect(response.status).to be 401
850
889
  end
851
890
  end
852
891
 
853
892
  context 'when HTTP method is defined and the underlying before hook expectation is not met' do
854
893
  let(:response) { post('/example?secret=password&namespace_secret=wrong_namespace_password') }
894
+
855
895
  it 'ends up in the endpoint' do
856
- expect(response.status).to eql 401
896
+ expect(response.status).to be 401
857
897
  end
858
898
  end
859
899
 
860
900
  context 'when HTTP method is defined and everything is like the before hooks expect' do
861
901
  let(:response) { post('/example?secret=password&namespace_secret=namespace_password') }
902
+
862
903
  it 'ends up in the endpoint' do
863
- expect(response.status).to eql 201
904
+ expect(response.status).to be 201
864
905
  end
865
906
  end
866
907
 
@@ -868,7 +909,7 @@ XML
868
909
  let(:response) { head('/example?id=504') }
869
910
 
870
911
  it 'responds with 401 because before expectations in before hooks are not met' do
871
- expect(response.status).to eql 401
912
+ expect(response.status).to be 401
872
913
  end
873
914
  end
874
915
 
@@ -876,7 +917,7 @@ XML
876
917
  let(:response) { head('/example?id=504&secret=password') }
877
918
 
878
919
  it 'responds with 200 because before hooks are not called' do
879
- expect(response.status).to eql 200
920
+ expect(response.status).to be 200
880
921
  end
881
922
  end
882
923
  end
@@ -893,7 +934,7 @@ XML
893
934
  end
894
935
 
895
936
  it 'returns a 200' do
896
- expect(last_response.status).to eql 200
937
+ expect(last_response.status).to be 200
897
938
  end
898
939
 
899
940
  it 'has an empty body' do
@@ -909,31 +950,33 @@ XML
909
950
  'example'
910
951
  end
911
952
  head '/example'
912
- expect(last_response.status).to eql 400
953
+ expect(last_response.status).to be 400
913
954
  end
914
955
  end
915
956
 
916
957
  context 'do_not_route_head!' do
917
- before :each do
958
+ before do
918
959
  subject.do_not_route_head!
919
960
  subject.get 'example' do
920
961
  'example'
921
962
  end
922
963
  end
964
+
923
965
  it 'options does not contain HEAD' do
924
966
  options '/example'
925
- expect(last_response.status).to eql 204
967
+ expect(last_response.status).to be 204
926
968
  expect(last_response.body).to eql ''
927
969
  expect(last_response.headers['Allow']).to eql 'OPTIONS, GET'
928
970
  end
971
+
929
972
  it 'does not allow HEAD on a GET request' do
930
973
  head '/example'
931
- expect(last_response.status).to eql 405
974
+ expect(last_response.status).to be 405
932
975
  end
933
976
  end
934
977
 
935
978
  context 'do_not_route_options!' do
936
- before :each do
979
+ before do
937
980
  subject.do_not_route_options!
938
981
  subject.get 'example' do
939
982
  'example'
@@ -942,19 +985,19 @@ XML
942
985
 
943
986
  it 'does not create an OPTIONS route' do
944
987
  options '/example'
945
- expect(last_response.status).to eql 405
988
+ expect(last_response.status).to be 405
946
989
  end
947
990
 
948
991
  it 'does not include OPTIONS in Allow header' do
949
992
  options '/example'
950
- expect(last_response.status).to eql 405
993
+ expect(last_response.status).to be 405
951
994
  expect(last_response.headers['Allow']).to eql 'GET, HEAD'
952
995
  end
953
996
  end
954
997
 
955
998
  describe '.compile!' do
956
999
  it 'requires the grape/eager_load file' do
957
- expect(app).to receive(:require).with('grape/eager_load') { nil }
1000
+ expect(app).to receive(:require).with('grape/eager_load').and_return(nil)
958
1001
  app.compile!
959
1002
  end
960
1003
 
@@ -976,7 +1019,7 @@ XML
976
1019
  context 'when the app was mounted' do
977
1020
  it 'returns the first mounted instance' do
978
1021
  mounted_app = app
979
- Class.new(Grape::API) do
1022
+ Class.new(described_class) do
980
1023
  namespace 'new_namespace' do
981
1024
  mount mounted_app
982
1025
  end
@@ -1004,6 +1047,7 @@ XML
1004
1047
  end
1005
1048
  subject.namespace :blah do
1006
1049
  before { @foo = 'foo' }
1050
+
1007
1051
  get '/' do
1008
1052
  "blah - #{@foo}"
1009
1053
  end
@@ -1045,7 +1089,7 @@ XML
1045
1089
  @var ||= 'default'
1046
1090
  end
1047
1091
 
1048
- expect(m).to receive(:do_something!).exactly(2).times
1092
+ expect(m).to receive(:do_something!).twice
1049
1093
  get '/'
1050
1094
  expect(last_response.body).to eql 'default'
1051
1095
  end
@@ -1061,21 +1105,23 @@ XML
1061
1105
  end
1062
1106
  subject.resource ':id' do
1063
1107
  before { a.do_something! }
1108
+
1064
1109
  before_validation { b.do_something! }
1065
1110
  after_validation { c.do_something! }
1066
1111
  after { d.do_something! }
1112
+
1067
1113
  get do
1068
1114
  'got it'
1069
1115
  end
1070
1116
  end
1071
1117
 
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
1118
+ expect(a).to receive(:do_something!).once
1119
+ expect(b).to receive(:do_something!).once
1120
+ expect(c).to receive(:do_something!).once
1121
+ expect(d).to receive(:do_something!).once
1076
1122
 
1077
1123
  get '/123'
1078
- expect(last_response.status).to eql 200
1124
+ expect(last_response.status).to be 200
1079
1125
  expect(last_response.body).to eql 'got it'
1080
1126
  end
1081
1127
 
@@ -1090,21 +1136,23 @@ XML
1090
1136
  end
1091
1137
  subject.resource ':id' do
1092
1138
  before { a.do_something! }
1139
+
1093
1140
  before_validation { b.do_something! }
1094
1141
  after_validation { c.do_something! }
1095
1142
  after { d.do_something! }
1143
+
1096
1144
  get do
1097
1145
  'got it'
1098
1146
  end
1099
1147
  end
1100
1148
 
1101
- expect(a).to receive(:do_something!).exactly(1).times
1102
- expect(b).to receive(:do_something!).exactly(1).times
1149
+ expect(a).to receive(:do_something!).once
1150
+ expect(b).to receive(:do_something!).once
1103
1151
  expect(c).to receive(:do_something!).exactly(0).times
1104
1152
  expect(d).to receive(:do_something!).exactly(0).times
1105
1153
 
1106
1154
  get '/abc'
1107
- expect(last_response.status).to eql 400
1155
+ expect(last_response.status).to be 400
1108
1156
  expect(last_response.body).to eql 'id is invalid'
1109
1157
  end
1110
1158
 
@@ -1120,21 +1168,23 @@ XML
1120
1168
  end
1121
1169
  subject.resource ':id' do
1122
1170
  before { a.here(i += 1) }
1171
+
1123
1172
  before_validation { b.here(i += 1) }
1124
1173
  after_validation { c.here(i += 1) }
1125
1174
  after { d.here(i += 1) }
1175
+
1126
1176
  get do
1127
1177
  'got it'
1128
1178
  end
1129
1179
  end
1130
1180
 
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
1181
+ expect(a).to receive(:here).with(1).once
1182
+ expect(b).to receive(:here).with(2).once
1183
+ expect(c).to receive(:here).with(3).once
1184
+ expect(d).to receive(:here).with(4).once
1135
1185
 
1136
1186
  get '/123'
1137
- expect(last_response.status).to eql 200
1187
+ expect(last_response.status).to be 200
1138
1188
  expect(last_response.body).to eql 'got it'
1139
1189
  end
1140
1190
  end
@@ -1151,7 +1201,7 @@ XML
1151
1201
 
1152
1202
  it 'does not set Cache-Control' do
1153
1203
  get '/foo'
1154
- expect(last_response.headers['Cache-Control']).to eq(nil)
1204
+ expect(last_response.headers['Cache-Control']).to be_nil
1155
1205
  end
1156
1206
 
1157
1207
  it 'sets content type for xml' do
@@ -1176,7 +1226,7 @@ XML
1176
1226
 
1177
1227
  it 'returns raw data when content type binary' do
1178
1228
  image_filename = 'grape.png'
1179
- file = File.open(image_filename, 'rb', &:read)
1229
+ file = File.binread(image_filename)
1180
1230
  subject.format :binary
1181
1231
  subject.get('/binary_file') { File.binread(image_filename) }
1182
1232
  get '/binary_file'
@@ -1208,7 +1258,7 @@ XML
1208
1258
  get '/stream', {}, 'HTTP_VERSION' => 'HTTP/1.1', 'SERVER_PROTOCOL' => 'HTTP/1.1'
1209
1259
 
1210
1260
  expect(last_response.headers['Content-Type']).to eq('text/plain')
1211
- expect(last_response.headers['Content-Length']).to eq(nil)
1261
+ expect(last_response.headers['Content-Length']).to be_nil
1212
1262
  expect(last_response.headers['Cache-Control']).to eq('no-cache')
1213
1263
  expect(last_response.headers['Transfer-Encoding']).to eq('chunked')
1214
1264
 
@@ -1225,7 +1275,7 @@ XML
1225
1275
  subject.format :json
1226
1276
  subject.get('/error') { error!('error in json', 500) }
1227
1277
  get '/error.json'
1228
- expect(last_response.status).to eql 500
1278
+ expect(last_response.status).to be 500
1229
1279
  expect(last_response.headers['Content-Type']).to eql 'application/json'
1230
1280
  end
1231
1281
 
@@ -1233,7 +1283,7 @@ XML
1233
1283
  subject.format :xml
1234
1284
  subject.get('/error') { error!('error in xml', 500) }
1235
1285
  get '/error'
1236
- expect(last_response.status).to eql 500
1286
+ expect(last_response.status).to be 500
1237
1287
  expect(last_response.headers['Content-Type']).to eql 'application/xml'
1238
1288
  end
1239
1289
 
@@ -1492,9 +1542,9 @@ XML
1492
1542
  end
1493
1543
  subject.get(:hello) { 'Hello, world.' }
1494
1544
  get '/hello'
1495
- expect(last_response.status).to eql 401
1545
+ expect(last_response.status).to be 401
1496
1546
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1497
- expect(last_response.status).to eql 200
1547
+ expect(last_response.status).to be 200
1498
1548
  end
1499
1549
 
1500
1550
  it 'is scopable' do
@@ -1508,9 +1558,9 @@ XML
1508
1558
  end
1509
1559
 
1510
1560
  get '/hello'
1511
- expect(last_response.status).to eql 200
1561
+ expect(last_response.status).to be 200
1512
1562
  get '/admin/hello'
1513
- expect(last_response.status).to eql 401
1563
+ expect(last_response.status).to be 401
1514
1564
  end
1515
1565
 
1516
1566
  it 'is callable via .auth as well' do
@@ -1520,9 +1570,9 @@ XML
1520
1570
 
1521
1571
  subject.get(:hello) { 'Hello, world.' }
1522
1572
  get '/hello'
1523
- expect(last_response.status).to eql 401
1573
+ expect(last_response.status).to be 401
1524
1574
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1525
- expect(last_response.status).to eql 200
1575
+ expect(last_response.status).to be 200
1526
1576
  end
1527
1577
 
1528
1578
  it 'has access to the current endpoint' do
@@ -1536,7 +1586,7 @@ XML
1536
1586
 
1537
1587
  subject.get(:hello) { 'Hello, world.' }
1538
1588
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1539
- expect(basic_auth_context).to be_a_kind_of(Grape::Endpoint)
1589
+ expect(basic_auth_context).to be_a(Grape::Endpoint)
1540
1590
  end
1541
1591
 
1542
1592
  it 'has access to helper methods' do
@@ -1552,9 +1602,9 @@ XML
1552
1602
 
1553
1603
  subject.get(:hello) { 'Hello, world.' }
1554
1604
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1555
- expect(last_response.status).to eql 200
1605
+ expect(last_response.status).to be 200
1556
1606
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('disallow', 'whatever')
1557
- expect(last_response.status).to eql 401
1607
+ expect(last_response.status).to be 401
1558
1608
  end
1559
1609
 
1560
1610
  it 'can set instance variables accessible to routes' do
@@ -1566,44 +1616,36 @@ XML
1566
1616
 
1567
1617
  subject.get(:hello) { @hello }
1568
1618
  get '/hello', {}, 'HTTP_AUTHORIZATION' => encode_basic_auth('allow', 'whatever')
1569
- expect(last_response.status).to eql 200
1619
+ expect(last_response.status).to be 200
1570
1620
  expect(last_response.body).to eql 'Hello, world.'
1571
1621
  end
1572
1622
  end
1573
1623
 
1574
1624
  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
1625
  it 'returns an instance of Logger class by default' do
1585
1626
  expect(subject.logger.class).to eql Logger
1586
1627
  end
1587
1628
 
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
1629
+ context 'with a custom logger' do
1630
+ subject do
1631
+ Class.new(described_class) do
1632
+ def self.io
1633
+ @io ||= StringIO.new
1634
+ end
1635
+ logger ::Logger.new(io)
1636
+ end
1637
+ end
1594
1638
 
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'
1639
+ it 'exposes its interaface' do
1640
+ message = 'this will be logged'
1641
+ subject.logger.info message
1642
+ expect(subject.io.string).to include(message)
1643
+ end
1602
1644
  end
1603
1645
 
1604
1646
  it 'does not unnecessarily retain duplicate setup blocks' do
1605
1647
  subject.logger
1606
- expect { subject.logger }.to_not change(subject.instance_variable_get(:@setup), :size)
1648
+ expect { subject.logger }.not_to change(subject.instance_variable_get(:@setup), :size)
1607
1649
  end
1608
1650
  end
1609
1651
 
@@ -1729,13 +1771,13 @@ XML
1729
1771
  end
1730
1772
 
1731
1773
  get '/new/abc'
1732
- expect(last_response.status).to eql 404
1774
+ expect(last_response.status).to be 404
1733
1775
  get '/legacy/abc'
1734
- expect(last_response.status).to eql 200
1776
+ expect(last_response.status).to be 200
1735
1777
  get '/legacy/def'
1736
- expect(last_response.status).to eql 404
1778
+ expect(last_response.status).to be 404
1737
1779
  get '/new/def'
1738
- expect(last_response.status).to eql 200
1780
+ expect(last_response.status).to be 200
1739
1781
  end
1740
1782
  end
1741
1783
 
@@ -1955,8 +1997,8 @@ XML
1955
1997
  end
1956
1998
 
1957
1999
  context 'with multiple apis' do
1958
- let(:a) { Class.new(Grape::API) }
1959
- let(:b) { Class.new(Grape::API) }
2000
+ let(:a) { Class.new(described_class) }
2001
+ let(:b) { Class.new(described_class) }
1960
2002
 
1961
2003
  before do
1962
2004
  a.helpers do
@@ -1990,7 +2032,7 @@ XML
1990
2032
  raise 'rain!'
1991
2033
  end
1992
2034
  get '/exception'
1993
- expect(last_response.status).to eql 500
2035
+ expect(last_response.status).to be 500
1994
2036
  expect(last_response.body).to eq 'rain!'
1995
2037
  end
1996
2038
 
@@ -2002,7 +2044,7 @@ XML
2002
2044
  raise 'rain!'
2003
2045
  end
2004
2046
  get '/exception'
2005
- expect(last_response.status).to eql 500
2047
+ expect(last_response.status).to be 500
2006
2048
  expect(last_response.body).to eq({ error: 'rain!' }.to_json)
2007
2049
  end
2008
2050
 
@@ -2012,7 +2054,7 @@ XML
2012
2054
  subject.get('/unrescued') { raise 'beefcake' }
2013
2055
 
2014
2056
  get '/rescued'
2015
- expect(last_response.status).to eql 500
2057
+ expect(last_response.status).to be 500
2016
2058
 
2017
2059
  expect { get '/unrescued' }.to raise_error(RuntimeError, 'beefcake')
2018
2060
  end
@@ -2031,10 +2073,10 @@ XML
2031
2073
  subject.get('/standard_error') { raise StandardError }
2032
2074
 
2033
2075
  get '/child_of_standard_error'
2034
- expect(last_response.status).to eql 402
2076
+ expect(last_response.status).to be 402
2035
2077
 
2036
2078
  get '/standard_error'
2037
- expect(last_response.status).to eql 401
2079
+ expect(last_response.status).to be 401
2038
2080
  end
2039
2081
 
2040
2082
  context 'CustomError subclass of Grape::Exceptions::Base' do
@@ -2075,7 +2117,7 @@ XML
2075
2117
  subject.get('/formatter_exception') { 'Hello world' }
2076
2118
 
2077
2119
  get '/formatter_exception'
2078
- expect(last_response.status).to eql 500
2120
+ expect(last_response.status).to be 500
2079
2121
  expect(last_response.body).to eq('Formatter Error')
2080
2122
  end
2081
2123
 
@@ -2085,7 +2127,7 @@ XML
2085
2127
 
2086
2128
  expect_any_instance_of(Grape::Middleware::Error).to receive(:default_rescue_handler).and_call_original
2087
2129
  get '/'
2088
- expect(last_response.status).to eql 500
2130
+ expect(last_response.status).to be 500
2089
2131
  expect(last_response.body).to eql 'Invalid response'
2090
2132
  end
2091
2133
  end
@@ -2099,14 +2141,16 @@ XML
2099
2141
  raise 'rain!'
2100
2142
  end
2101
2143
  get '/exception'
2102
- expect(last_response.status).to eql 202
2144
+ expect(last_response.status).to be 202
2103
2145
  expect(last_response.body).to eq('rescued from rain!')
2104
2146
  end
2105
2147
 
2106
2148
  context 'custom errors' do
2107
2149
  before do
2108
2150
  class ConnectionError < RuntimeError; end
2151
+
2109
2152
  class DatabaseError < RuntimeError; end
2153
+
2110
2154
  class CommunicationError < StandardError; end
2111
2155
  end
2112
2156
 
@@ -2118,9 +2162,10 @@ XML
2118
2162
  raise ConnectionError
2119
2163
  end
2120
2164
  get '/exception'
2121
- expect(last_response.status).to eql 500
2165
+ expect(last_response.status).to be 500
2122
2166
  expect(last_response.body).to eq('rescued from ConnectionError')
2123
2167
  end
2168
+
2124
2169
  it 'rescues a specific error' do
2125
2170
  subject.rescue_from ConnectionError do |e|
2126
2171
  rack_response("rescued from #{e.class.name}", 500)
@@ -2129,9 +2174,10 @@ XML
2129
2174
  raise ConnectionError
2130
2175
  end
2131
2176
  get '/exception'
2132
- expect(last_response.status).to eql 500
2177
+ expect(last_response.status).to be 500
2133
2178
  expect(last_response.body).to eq('rescued from ConnectionError')
2134
2179
  end
2180
+
2135
2181
  it 'rescues a subclass of an error by default' do
2136
2182
  subject.rescue_from RuntimeError do |e|
2137
2183
  rack_response("rescued from #{e.class.name}", 500)
@@ -2140,9 +2186,10 @@ XML
2140
2186
  raise ConnectionError
2141
2187
  end
2142
2188
  get '/exception'
2143
- expect(last_response.status).to eql 500
2189
+ expect(last_response.status).to be 500
2144
2190
  expect(last_response.body).to eq('rescued from ConnectionError')
2145
2191
  end
2192
+
2146
2193
  it 'rescues multiple specific errors' do
2147
2194
  subject.rescue_from ConnectionError do |e|
2148
2195
  rack_response("rescued from #{e.class.name}", 500)
@@ -2157,12 +2204,13 @@ XML
2157
2204
  raise DatabaseError
2158
2205
  end
2159
2206
  get '/connection'
2160
- expect(last_response.status).to eql 500
2207
+ expect(last_response.status).to be 500
2161
2208
  expect(last_response.body).to eq('rescued from ConnectionError')
2162
2209
  get '/database'
2163
- expect(last_response.status).to eql 500
2210
+ expect(last_response.status).to be 500
2164
2211
  expect(last_response.body).to eq('rescued from DatabaseError')
2165
2212
  end
2213
+
2166
2214
  it 'does not rescue a different error' do
2167
2215
  subject.rescue_from RuntimeError do |e|
2168
2216
  rack_response("rescued from #{e.class.name}", 500)
@@ -2228,7 +2276,7 @@ XML
2228
2276
  subject.rescue_from :all, with: :not_exist_method
2229
2277
  subject.get('/rescue_method') { raise StandardError }
2230
2278
 
2231
- expect { get '/rescue_method' }.to raise_error(NoMethodError, 'undefined method `not_exist_method\'')
2279
+ expect { get '/rescue_method' }.to raise_error(NoMethodError, /^undefined method 'not_exist_method'/)
2232
2280
  end
2233
2281
 
2234
2282
  it 'correctly chooses exception handler if :all handler is specified' do
@@ -2262,6 +2310,7 @@ XML
2262
2310
  module ApiSpec
2263
2311
  module APIErrors
2264
2312
  class ParentError < StandardError; end
2313
+
2265
2314
  class ChildError < ParentError; end
2266
2315
  end
2267
2316
  end
@@ -2282,9 +2331,9 @@ XML
2282
2331
  end
2283
2332
 
2284
2333
  get '/caught_child'
2285
- expect(last_response.status).to eql 500
2334
+ expect(last_response.status).to be 500
2286
2335
  get '/caught_parent'
2287
- expect(last_response.status).to eql 500
2336
+ expect(last_response.status).to be 500
2288
2337
  expect { get '/uncaught_parent' }.to raise_error(StandardError)
2289
2338
  end
2290
2339
 
@@ -2297,7 +2346,7 @@ XML
2297
2346
  end
2298
2347
 
2299
2348
  get '/caught_child'
2300
- expect(last_response.status).to eql 500
2349
+ expect(last_response.status).to be 500
2301
2350
  end
2302
2351
 
2303
2352
  it 'does not rescue child errors if rescue_subclasses is false' do
@@ -2392,7 +2441,7 @@ XML
2392
2441
  end
2393
2442
 
2394
2443
  context 'class' do
2395
- before :each do
2444
+ before do
2396
2445
  module ApiSpec
2397
2446
  class CustomErrorFormatter
2398
2447
  def self.call(message, _backtrace, _options, _env, _original_exception)
@@ -2401,6 +2450,7 @@ XML
2401
2450
  end
2402
2451
  end
2403
2452
  end
2453
+
2404
2454
  it 'returns a custom error format' do
2405
2455
  subject.rescue_from :all, backtrace: true
2406
2456
  subject.error_formatter :txt, ApiSpec::CustomErrorFormatter
@@ -2414,7 +2464,7 @@ XML
2414
2464
 
2415
2465
  describe 'with' do
2416
2466
  context 'class' do
2417
- before :each do
2467
+ before do
2418
2468
  module ApiSpec
2419
2469
  class CustomErrorFormatter
2420
2470
  def self.call(message, _backtrace, _option, _env, _original_exception)
@@ -2444,6 +2494,7 @@ XML
2444
2494
  get '/exception'
2445
2495
  expect(last_response.body).to eql '{"error":"rain!"}'
2446
2496
  end
2497
+
2447
2498
  it 'rescues all errors and return :json with backtrace' do
2448
2499
  subject.rescue_from :all, backtrace: true
2449
2500
  subject.format :json
@@ -2455,6 +2506,7 @@ XML
2455
2506
  expect(json['error']).to eql 'rain!'
2456
2507
  expect(json['backtrace'].length).to be > 0
2457
2508
  end
2509
+
2458
2510
  it 'rescues error! and return txt' do
2459
2511
  subject.format :txt
2460
2512
  subject.get '/error' do
@@ -2463,23 +2515,26 @@ XML
2463
2515
  get '/error'
2464
2516
  expect(last_response.body).to eql 'Access Denied'
2465
2517
  end
2518
+
2466
2519
  context 'with json format' do
2467
2520
  before { subject.format :json }
2468
2521
 
2522
+ after do
2523
+ get '/error'
2524
+ expect(last_response.body).to eql('{"error":"failure"}')
2525
+ end
2526
+
2469
2527
  it 'rescues error! called with a string and returns json' do
2470
2528
  subject.get('/error') { error!(:failure, 401) }
2471
2529
  end
2530
+
2472
2531
  it 'rescues error! called with a symbol and returns json' do
2473
2532
  subject.get('/error') { error!(:failure, 401) }
2474
2533
  end
2534
+
2475
2535
  it 'rescues error! called with a hash and returns json' do
2476
2536
  subject.get('/error') { error!({ error: :failure }, 401) }
2477
2537
  end
2478
-
2479
- after do
2480
- get '/error'
2481
- expect(last_response.body).to eql('{"error":"failure"}')
2482
- end
2483
2538
  end
2484
2539
  end
2485
2540
 
@@ -2492,6 +2547,7 @@ XML
2492
2547
  get '/excel.xls'
2493
2548
  expect(last_response.content_type).to eq('application/vnd.ms-excel')
2494
2549
  end
2550
+
2495
2551
  it 'allows to override content-type' do
2496
2552
  subject.get :content do
2497
2553
  content_type 'text/javascript'
@@ -2500,6 +2556,7 @@ XML
2500
2556
  get '/content'
2501
2557
  expect(last_response.content_type).to eq('text/javascript')
2502
2558
  end
2559
+
2503
2560
  it 'removes existing content types' do
2504
2561
  subject.content_type :xls, 'application/vnd.ms-excel'
2505
2562
  subject.get :excel do
@@ -2517,24 +2574,27 @@ XML
2517
2574
 
2518
2575
  describe '.formatter' do
2519
2576
  context 'multiple formatters' do
2520
- before :each do
2577
+ before do
2521
2578
  subject.formatter :json, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
2522
2579
  subject.formatter :txt, ->(object, _env) { "custom_formatter: #{object[:some]}" }
2523
2580
  subject.get :simple do
2524
2581
  { some: 'hash' }
2525
2582
  end
2526
2583
  end
2584
+
2527
2585
  it 'sets one formatter' do
2528
2586
  get '/simple.json'
2529
2587
  expect(last_response.body).to eql '{"custom_formatter":"hash"}'
2530
2588
  end
2589
+
2531
2590
  it 'sets another formatter' do
2532
2591
  get '/simple.txt'
2533
2592
  expect(last_response.body).to eql 'custom_formatter: hash'
2534
2593
  end
2535
2594
  end
2595
+
2536
2596
  context 'custom formatter' do
2537
- before :each do
2597
+ before do
2538
2598
  subject.content_type :json, 'application/json'
2539
2599
  subject.content_type :custom, 'application/custom'
2540
2600
  subject.formatter :custom, ->(object, _env) { "{\"custom_formatter\":\"#{object[:some]}\"}" }
@@ -2542,15 +2602,18 @@ XML
2542
2602
  { some: 'hash' }
2543
2603
  end
2544
2604
  end
2605
+
2545
2606
  it 'uses json' do
2546
2607
  get '/simple.json'
2547
2608
  expect(last_response.body).to eql '{"some":"hash"}'
2548
2609
  end
2610
+
2549
2611
  it 'uses custom formatter' do
2550
2612
  get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
2551
2613
  expect(last_response.body).to eql '{"custom_formatter":"hash"}'
2552
2614
  end
2553
2615
  end
2616
+
2554
2617
  context 'custom formatter class' do
2555
2618
  module ApiSpec
2556
2619
  module CustomFormatter
@@ -2559,7 +2622,7 @@ XML
2559
2622
  end
2560
2623
  end
2561
2624
  end
2562
- before :each do
2625
+ before do
2563
2626
  subject.content_type :json, 'application/json'
2564
2627
  subject.content_type :custom, 'application/custom'
2565
2628
  subject.formatter :custom, ApiSpec::CustomFormatter
@@ -2567,10 +2630,12 @@ XML
2567
2630
  { some: 'hash' }
2568
2631
  end
2569
2632
  end
2633
+
2570
2634
  it 'uses json' do
2571
2635
  get '/simple.json'
2572
2636
  expect(last_response.body).to eql '{"some":"hash"}'
2573
2637
  end
2638
+
2574
2639
  it 'uses custom formatter' do
2575
2640
  get '/simple.custom', 'HTTP_ACCEPT' => 'application/custom'
2576
2641
  expect(last_response.body).to eql '{"custom_formatter":"hash"}'
@@ -2588,8 +2653,9 @@ XML
2588
2653
  expect(last_response.status).to eq(201)
2589
2654
  expect(last_response.body).to eq('{"x":42}')
2590
2655
  end
2656
+
2591
2657
  context 'lambda parser' do
2592
- before :each do
2658
+ before do
2593
2659
  subject.content_type :txt, 'text/plain'
2594
2660
  subject.content_type :custom, 'text/custom'
2595
2661
  subject.parser :custom, ->(object, _env) { { object.to_sym => object.to_s.reverse } }
@@ -2597,6 +2663,7 @@ XML
2597
2663
  params[:simple]
2598
2664
  end
2599
2665
  end
2666
+
2600
2667
  ['text/custom', 'text/custom; charset=UTF-8'].each do |content_type|
2601
2668
  it "uses parser for #{content_type}" do
2602
2669
  put '/simple', 'simple', 'CONTENT_TYPE' => content_type
@@ -2605,6 +2672,7 @@ XML
2605
2672
  end
2606
2673
  end
2607
2674
  end
2675
+
2608
2676
  context 'custom parser class' do
2609
2677
  module ApiSpec
2610
2678
  module CustomParser
@@ -2613,7 +2681,7 @@ XML
2613
2681
  end
2614
2682
  end
2615
2683
  end
2616
- before :each do
2684
+ before do
2617
2685
  subject.content_type :txt, 'text/plain'
2618
2686
  subject.content_type :custom, 'text/custom'
2619
2687
  subject.parser :custom, ApiSpec::CustomParser
@@ -2621,12 +2689,14 @@ XML
2621
2689
  params[:simple]
2622
2690
  end
2623
2691
  end
2692
+
2624
2693
  it 'uses custom parser' do
2625
2694
  put '/simple', 'simple', 'CONTENT_TYPE' => 'text/custom'
2626
2695
  expect(last_response.status).to eq(200)
2627
2696
  expect(last_response.body).to eql 'elpmis'
2628
2697
  end
2629
2698
  end
2699
+
2630
2700
  if Object.const_defined? :MultiXml
2631
2701
  context 'multi_xml' do
2632
2702
  it "doesn't parse yaml" do
@@ -2651,12 +2721,13 @@ XML
2651
2721
  end
2652
2722
  end
2653
2723
  context 'none parser class' do
2654
- before :each do
2724
+ before do
2655
2725
  subject.parser :json, nil
2656
2726
  subject.put 'data' do
2657
2727
  "body: #{env['api.request.body']}"
2658
2728
  end
2659
2729
  end
2730
+
2660
2731
  it 'does not parse data' do
2661
2732
  put '/data', 'not valid json', 'CONTENT_TYPE' => 'application/json'
2662
2733
  expect(last_response.status).to eq(200)
@@ -2666,10 +2737,11 @@ XML
2666
2737
  end
2667
2738
 
2668
2739
  describe '.default_format' do
2669
- before :each do
2740
+ before do
2670
2741
  subject.format :json
2671
2742
  subject.default_format :json
2672
2743
  end
2744
+
2673
2745
  it 'returns data in default format' do
2674
2746
  subject.get '/data' do
2675
2747
  { x: 42 }
@@ -2678,6 +2750,7 @@ XML
2678
2750
  expect(last_response.status).to eq(200)
2679
2751
  expect(last_response.body).to eq('{"x":42}')
2680
2752
  end
2753
+
2681
2754
  it 'parses data in default format' do
2682
2755
  subject.post '/data' do
2683
2756
  { x: params[:x] }
@@ -2696,16 +2769,18 @@ XML
2696
2769
  raise 'rain!'
2697
2770
  end
2698
2771
  get '/exception'
2699
- expect(last_response.status).to eql 200
2772
+ expect(last_response.status).to be 200
2700
2773
  end
2774
+
2701
2775
  it 'has a default error status' do
2702
2776
  subject.rescue_from :all
2703
2777
  subject.get '/exception' do
2704
2778
  raise 'rain!'
2705
2779
  end
2706
2780
  get '/exception'
2707
- expect(last_response.status).to eql 500
2781
+ expect(last_response.status).to be 500
2708
2782
  end
2783
+
2709
2784
  it 'uses the default error status in error!' do
2710
2785
  subject.rescue_from :all
2711
2786
  subject.default_error_status 400
@@ -2713,7 +2788,7 @@ XML
2713
2788
  error! 'rain!'
2714
2789
  end
2715
2790
  get '/exception'
2716
- expect(last_response.status).to eql 400
2791
+ expect(last_response.status).to be 400
2717
2792
  end
2718
2793
  end
2719
2794
 
@@ -2739,7 +2814,7 @@ XML
2739
2814
  end
2740
2815
 
2741
2816
  get '/exception'
2742
- expect(last_response.status).to eql 408
2817
+ expect(last_response.status).to be 408
2743
2818
  expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
2744
2819
  end
2745
2820
 
@@ -2750,7 +2825,7 @@ XML
2750
2825
  end
2751
2826
 
2752
2827
  get '/exception'
2753
- expect(last_response.status).to eql 408
2828
+ expect(last_response.status).to be 408
2754
2829
  expect(last_response.body).to eql({ code: 408, static: 'some static text' }.to_json)
2755
2830
  end
2756
2831
  end
@@ -2761,12 +2836,14 @@ XML
2761
2836
  expect(subject.routes).to eq([])
2762
2837
  end
2763
2838
  end
2839
+
2764
2840
  describe 'single method api structure' do
2765
- before(:each) do
2841
+ before do
2766
2842
  subject.get :ping do
2767
2843
  'pong'
2768
2844
  end
2769
2845
  end
2846
+
2770
2847
  it 'returns one route' do
2771
2848
  expect(subject.routes.size).to eq(1)
2772
2849
  route = subject.routes[0]
@@ -2775,8 +2852,9 @@ XML
2775
2852
  expect(route.request_method).to eq('GET')
2776
2853
  end
2777
2854
  end
2855
+
2778
2856
  describe 'api structure with two versions and a namespace' do
2779
- before :each do
2857
+ before do
2780
2858
  subject.version 'v1', using: :path
2781
2859
  subject.get 'version' do
2782
2860
  api.version
@@ -2792,30 +2870,37 @@ XML
2792
2870
  end
2793
2871
  end
2794
2872
  end
2873
+
2795
2874
  it 'returns the latest version set' do
2796
2875
  expect(subject.version).to eq('v2')
2797
2876
  end
2877
+
2798
2878
  it 'returns versions' do
2799
2879
  expect(subject.versions).to eq(%w[v1 v2])
2800
2880
  end
2881
+
2801
2882
  it 'sets route paths' do
2802
2883
  expect(subject.routes.size).to be >= 2
2803
2884
  expect(subject.routes[0].path).to eq('/:version/version(.:format)')
2804
2885
  expect(subject.routes[1].path).to eq('/p/:version/n1/n2/version(.:format)')
2805
2886
  end
2887
+
2806
2888
  it 'sets route versions' do
2807
2889
  expect(subject.routes[0].version).to eq('v1')
2808
2890
  expect(subject.routes[1].version).to eq('v2')
2809
2891
  end
2892
+
2810
2893
  it 'sets a nested namespace' do
2811
2894
  expect(subject.routes[1].namespace).to eq('/n1/n2')
2812
2895
  end
2896
+
2813
2897
  it 'sets prefix' do
2814
2898
  expect(subject.routes[1].prefix).to eq('p')
2815
2899
  end
2816
2900
  end
2901
+
2817
2902
  describe 'api structure with additional parameters' do
2818
- before(:each) do
2903
+ before do
2819
2904
  subject.params do
2820
2905
  requires :token, desc: 'a token'
2821
2906
  optional :limit, desc: 'the limit'
@@ -2824,14 +2909,17 @@ XML
2824
2909
  params[:string].split(params[:token], (params[:limit] || 0).to_i)
2825
2910
  end
2826
2911
  end
2912
+
2827
2913
  it 'splits a string' do
2828
2914
  get '/split/a,b,c.json', token: ','
2829
2915
  expect(last_response.body).to eq('["a","b","c"]')
2830
2916
  end
2917
+
2831
2918
  it 'splits a string with limit' do
2832
2919
  get '/split/a,b,c.json', token: ',', limit: '2'
2833
2920
  expect(last_response.body).to eq('["a","b,c"]')
2834
2921
  end
2922
+
2835
2923
  it 'sets params' do
2836
2924
  expect(subject.routes.map do |route|
2837
2925
  { params: route.params }
@@ -2846,8 +2934,9 @@ XML
2846
2934
  ]
2847
2935
  end
2848
2936
  end
2937
+
2849
2938
  describe 'api structure with multiple apis' do
2850
- before(:each) do
2939
+ before do
2851
2940
  subject.params do
2852
2941
  requires :one, desc: 'a token'
2853
2942
  optional :two, desc: 'the limit'
@@ -2862,6 +2951,7 @@ XML
2862
2951
  subject.get 'two' do
2863
2952
  end
2864
2953
  end
2954
+
2865
2955
  it 'sets params' do
2866
2956
  expect(subject.routes.map do |route|
2867
2957
  { params: route.params }
@@ -2881,8 +2971,9 @@ XML
2881
2971
  ]
2882
2972
  end
2883
2973
  end
2974
+
2884
2975
  describe 'api structure with an api without params' do
2885
- before(:each) do
2976
+ before do
2886
2977
  subject.params do
2887
2978
  requires :one, desc: 'a token'
2888
2979
  optional :two, desc: 'the limit'
@@ -2893,6 +2984,7 @@ XML
2893
2984
  subject.get 'two' do
2894
2985
  end
2895
2986
  end
2987
+
2896
2988
  it 'sets params' do
2897
2989
  expect(subject.routes.map do |route|
2898
2990
  { params: route.params }
@@ -2909,17 +3001,20 @@ XML
2909
3001
  ]
2910
3002
  end
2911
3003
  end
3004
+
2912
3005
  describe 'api with a custom route setting' do
2913
- before(:each) do
3006
+ before do
2914
3007
  subject.route_setting :custom, key: 'value'
2915
3008
  subject.get 'one'
2916
3009
  end
3010
+
2917
3011
  it 'exposed' do
2918
3012
  expect(subject.routes.count).to eq 1
2919
3013
  route = subject.routes.first
2920
3014
  expect(route.settings[:custom]).to eq(key: 'value')
2921
3015
  end
2922
3016
  end
3017
+
2923
3018
  describe 'status' do
2924
3019
  it 'can be set to arbitrary Integer value' do
2925
3020
  subject.get '/foo' do
@@ -2928,6 +3023,7 @@ XML
2928
3023
  get '/foo'
2929
3024
  expect(last_response.status).to eq 210
2930
3025
  end
3026
+
2931
3027
  it 'can be set with a status code symbol' do
2932
3028
  subject.get '/foo' do
2933
3029
  status :see_other
@@ -2942,10 +3038,12 @@ XML
2942
3038
  it 'empty array of routes' do
2943
3039
  expect(subject.routes).to eq([])
2944
3040
  end
3041
+
2945
3042
  it 'empty array of routes' do
2946
3043
  subject.desc 'grape api'
2947
3044
  expect(subject.routes).to eq([])
2948
3045
  end
3046
+
2949
3047
  it 'describes a method' do
2950
3048
  subject.desc 'first method'
2951
3049
  subject.get :first
@@ -2954,8 +3052,9 @@ XML
2954
3052
  expect(route.description).to eq('first method')
2955
3053
  expect(route.route_foo).to be_nil
2956
3054
  expect(route.params).to eq({})
2957
- expect(route.options).to be_a_kind_of(Hash)
3055
+ expect(route.options).to be_a(Hash)
2958
3056
  end
3057
+
2959
3058
  it 'has params which does not include format and version as named captures' do
2960
3059
  subject.version :v1, using: :path
2961
3060
  subject.get :first
@@ -2963,6 +3062,7 @@ XML
2963
3062
  expect(param_keys).not_to include('format')
2964
3063
  expect(param_keys).not_to include('version')
2965
3064
  end
3065
+
2966
3066
  it 'describes methods separately' do
2967
3067
  subject.desc 'first method'
2968
3068
  subject.get :first
@@ -2976,6 +3076,7 @@ XML
2976
3076
  { description: 'second method', params: {} }
2977
3077
  ]
2978
3078
  end
3079
+
2979
3080
  it 'resets desc' do
2980
3081
  subject.desc 'first method'
2981
3082
  subject.get :first
@@ -2987,6 +3088,7 @@ XML
2987
3088
  { description: nil, params: {} }
2988
3089
  ]
2989
3090
  end
3091
+
2990
3092
  it 'namespaces and describe arbitrary parameters' do
2991
3093
  subject.namespace 'ns' do
2992
3094
  desc 'ns second', foo: 'bar'
@@ -2998,6 +3100,7 @@ XML
2998
3100
  { description: 'ns second', foo: 'bar', params: {} }
2999
3101
  ]
3000
3102
  end
3103
+
3001
3104
  it 'includes details' do
3002
3105
  subject.desc 'method', details: 'method details'
3003
3106
  subject.get 'method'
@@ -3007,6 +3110,7 @@ XML
3007
3110
  { description: 'method', details: 'method details', params: {} }
3008
3111
  ]
3009
3112
  end
3113
+
3010
3114
  it 'describes a method with parameters' do
3011
3115
  subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
3012
3116
  subject.get 'reverse' do
@@ -3018,6 +3122,7 @@ XML
3018
3122
  { description: 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } } }
3019
3123
  ]
3020
3124
  end
3125
+
3021
3126
  it 'does not inherit param descriptions in consequent namespaces' do
3022
3127
  subject.desc 'global description'
3023
3128
  subject.params do
@@ -3048,6 +3153,7 @@ XML
3048
3153
  } }
3049
3154
  ]
3050
3155
  end
3156
+
3051
3157
  it 'merges the parameters of the namespace with the parameters of the method' do
3052
3158
  subject.desc 'namespace'
3053
3159
  subject.params do
@@ -3072,6 +3178,7 @@ XML
3072
3178
  } }
3073
3179
  ]
3074
3180
  end
3181
+
3075
3182
  it 'merges the parameters of nested namespaces' do
3076
3183
  subject.desc 'ns1'
3077
3184
  subject.params do
@@ -3104,6 +3211,7 @@ XML
3104
3211
  } }
3105
3212
  ]
3106
3213
  end
3214
+
3107
3215
  it 'groups nested params and prevents overwriting of params with same name in different groups' do
3108
3216
  subject.desc 'method'
3109
3217
  subject.params do
@@ -3119,14 +3227,15 @@ XML
3119
3227
  subject.get 'method'
3120
3228
 
3121
3229
  expect(subject.routes.map(&:params)).to eq [{
3122
- 'group1' => { required: true, type: 'Array' },
3230
+ 'group1' => { required: true, type: 'Array' },
3123
3231
  'group1[param1]' => { required: false, desc: 'group1 param1 desc' },
3124
3232
  'group1[param2]' => { required: true, desc: 'group1 param2 desc' },
3125
- 'group2' => { required: true, type: 'Array' },
3233
+ 'group2' => { required: true, type: 'Array' },
3126
3234
  'group2[param1]' => { required: false, desc: 'group2 param1 desc' },
3127
3235
  'group2[param2]' => { required: true, desc: 'group2 param2 desc' }
3128
3236
  }]
3129
3237
  end
3238
+
3130
3239
  it 'uses full name of parameters in nested groups' do
3131
3240
  subject.desc 'nesting'
3132
3241
  subject.params do
@@ -3147,6 +3256,7 @@ XML
3147
3256
  } }
3148
3257
  ]
3149
3258
  end
3259
+
3150
3260
  it 'allows to set the type attribute on :group element' do
3151
3261
  subject.params do
3152
3262
  group :foo, type: Array do
@@ -3154,6 +3264,7 @@ XML
3154
3264
  end
3155
3265
  end
3156
3266
  end
3267
+
3157
3268
  it 'parses parameters when no description is given' do
3158
3269
  subject.params do
3159
3270
  requires :one_param, desc: 'one param'
@@ -3165,6 +3276,7 @@ XML
3165
3276
  { description: nil, params: { 'one_param' => { required: true, desc: 'one param' } } }
3166
3277
  ]
3167
3278
  end
3279
+
3168
3280
  it 'does not symbolize params' do
3169
3281
  subject.desc 'Reverses a string.', params: { 's' => { desc: 'string to reverse', type: 'string' } }
3170
3282
  subject.get 'reverse/:s' do
@@ -3223,7 +3335,7 @@ XML
3223
3335
  subject.version 'v1', using: :path
3224
3336
 
3225
3337
  subject.namespace :cool do
3226
- app = Class.new(Grape::API)
3338
+ app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3227
3339
  app.get('/awesome') do
3228
3340
  'yo'
3229
3341
  end
@@ -3239,12 +3351,12 @@ XML
3239
3351
  subject.version 'v1', using: :path
3240
3352
 
3241
3353
  subject.namespace :cool do
3242
- inner_app = Class.new(Grape::API)
3354
+ inner_app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3243
3355
  inner_app.get('/awesome') do
3244
3356
  'yo'
3245
3357
  end
3246
3358
 
3247
- app = Class.new(Grape::API)
3359
+ app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3248
3360
  app.mount inner_app
3249
3361
  mount app
3250
3362
  end
@@ -3259,7 +3371,7 @@ XML
3259
3371
  rack_response("rescued from #{e.message}", 202)
3260
3372
  end
3261
3373
 
3262
- app = Class.new(Grape::API)
3374
+ app = Class.new(described_class)
3263
3375
 
3264
3376
  subject.namespace :mounted do
3265
3377
  app.rescue_from ArgumentError
@@ -3268,15 +3380,16 @@ XML
3268
3380
  end
3269
3381
 
3270
3382
  get '/mounted/fail'
3271
- expect(last_response.status).to eql 202
3383
+ expect(last_response.status).to be 202
3272
3384
  expect(last_response.body).to eq('rescued from doh!')
3273
3385
  end
3386
+
3274
3387
  it 'prefers rescues defined by mounted if they rescue similar error class' do
3275
3388
  subject.rescue_from StandardError do
3276
3389
  rack_response('outer rescue')
3277
3390
  end
3278
3391
 
3279
- app = Class.new(Grape::API)
3392
+ app = Class.new(described_class)
3280
3393
 
3281
3394
  subject.namespace :mounted do
3282
3395
  rescue_from StandardError do
@@ -3289,12 +3402,13 @@ XML
3289
3402
  get '/mounted/fail'
3290
3403
  expect(last_response.body).to eq('inner rescue')
3291
3404
  end
3405
+
3292
3406
  it 'prefers rescues defined by mounted even if outer is more specific' do
3293
3407
  subject.rescue_from ArgumentError do
3294
3408
  rack_response('outer rescue')
3295
3409
  end
3296
3410
 
3297
- app = Class.new(Grape::API)
3411
+ app = Class.new(described_class)
3298
3412
 
3299
3413
  subject.namespace :mounted do
3300
3414
  rescue_from StandardError do
@@ -3307,12 +3421,13 @@ XML
3307
3421
  get '/mounted/fail'
3308
3422
  expect(last_response.body).to eq('inner rescue')
3309
3423
  end
3424
+
3310
3425
  it 'prefers more specific rescues defined by mounted' do
3311
3426
  subject.rescue_from StandardError do
3312
3427
  rack_response('outer rescue')
3313
3428
  end
3314
3429
 
3315
- app = Class.new(Grape::API)
3430
+ app = Class.new(described_class)
3316
3431
 
3317
3432
  subject.namespace :mounted do
3318
3433
  rescue_from ArgumentError do
@@ -3329,19 +3444,19 @@ XML
3329
3444
 
3330
3445
  it 'collects the routes of the mounted api' do
3331
3446
  subject.namespace :cool do
3332
- app = Class.new(Grape::API)
3447
+ app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3333
3448
  app.get('/awesome') {}
3334
3449
  app.post('/sauce') {}
3335
3450
  mount app
3336
3451
  end
3337
3452
  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})
3453
+ expect(subject.routes.first.path).to match(%r{/cool/awesome})
3454
+ expect(subject.routes.last.path).to match(%r{/cool/sauce})
3340
3455
  end
3341
3456
 
3342
3457
  it 'mounts on a path' do
3343
3458
  subject.namespace :cool do
3344
- app = Class.new(Grape::API)
3459
+ app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3345
3460
  app.get '/awesome' do
3346
3461
  'sauce'
3347
3462
  end
@@ -3353,12 +3468,12 @@ XML
3353
3468
  end
3354
3469
 
3355
3470
  it 'mounts on a nested path' do
3356
- APP1 = Class.new(Grape::API)
3357
- APP2 = Class.new(Grape::API)
3471
+ APP1 = Class.new(described_class)
3472
+ APP2 = Class.new(described_class)
3358
3473
  APP2.get '/nice' do
3359
3474
  'play'
3360
3475
  end
3361
- # note that the reverse won't work, mount from outside-in
3476
+ # NOTE: that the reverse won't work, mount from outside-in
3362
3477
  APP3 = subject
3363
3478
  APP3.mount APP1 => '/app1'
3364
3479
  APP1.mount APP2 => '/app2'
@@ -3370,7 +3485,7 @@ XML
3370
3485
  end
3371
3486
 
3372
3487
  it 'responds to options' do
3373
- app = Class.new(Grape::API)
3488
+ app = Class.new(described_class)
3374
3489
  app.get '/colour' do
3375
3490
  'red'
3376
3491
  end
@@ -3384,21 +3499,21 @@ XML
3384
3499
  end
3385
3500
 
3386
3501
  get '/apples/colour'
3387
- expect(last_response.status).to eql 200
3502
+ expect(last_response.status).to be 200
3388
3503
  expect(last_response.body).to eq('red')
3389
3504
  options '/apples/colour'
3390
- expect(last_response.status).to eql 204
3505
+ expect(last_response.status).to be 204
3391
3506
  get '/apples/pears/colour'
3392
- expect(last_response.status).to eql 200
3507
+ expect(last_response.status).to be 200
3393
3508
  expect(last_response.body).to eq('green')
3394
3509
  options '/apples/pears/colour'
3395
- expect(last_response.status).to eql 204
3510
+ expect(last_response.status).to be 204
3396
3511
  end
3397
3512
 
3398
3513
  it 'responds to options with path versioning' do
3399
3514
  subject.version 'v1', using: :path
3400
3515
  subject.namespace :apples do
3401
- app = Class.new(Grape::API)
3516
+ app = Class.new(Grape::API) # rubocop:disable RSpec/DescribedClass
3402
3517
  app.get('/colour') do
3403
3518
  'red'
3404
3519
  end
@@ -3406,14 +3521,14 @@ XML
3406
3521
  end
3407
3522
 
3408
3523
  get '/v1/apples/colour'
3409
- expect(last_response.status).to eql 200
3524
+ expect(last_response.status).to be 200
3410
3525
  expect(last_response.body).to eq('red')
3411
3526
  options '/v1/apples/colour'
3412
- expect(last_response.status).to eql 204
3527
+ expect(last_response.status).to be 204
3413
3528
  end
3414
3529
 
3415
3530
  it 'mounts a versioned API with nested resources' do
3416
- api = Class.new(Grape::API) do
3531
+ api = Class.new(described_class) do
3417
3532
  version 'v1'
3418
3533
  resources :users do
3419
3534
  get :hello do
@@ -3428,7 +3543,7 @@ XML
3428
3543
  end
3429
3544
 
3430
3545
  it 'mounts a prefixed API with nested resources' do
3431
- api = Class.new(Grape::API) do
3546
+ api = Class.new(described_class) do
3432
3547
  prefix 'api'
3433
3548
  resources :users do
3434
3549
  get :hello do
@@ -3443,7 +3558,7 @@ XML
3443
3558
  end
3444
3559
 
3445
3560
  it 'applies format to a mounted API with nested resources' do
3446
- api = Class.new(Grape::API) do
3561
+ api = Class.new(described_class) do
3447
3562
  format :json
3448
3563
  resources :users do
3449
3564
  get do
@@ -3458,7 +3573,7 @@ XML
3458
3573
  end
3459
3574
 
3460
3575
  it 'applies auth to a mounted API with nested resources' do
3461
- api = Class.new(Grape::API) do
3576
+ api = Class.new(described_class) do
3462
3577
  format :json
3463
3578
  http_basic do |username, password|
3464
3579
  username == 'username' && password == 'password'
@@ -3479,7 +3594,7 @@ XML
3479
3594
  end
3480
3595
 
3481
3596
  it 'mounts multiple versioned APIs with nested resources' do
3482
- api1 = Class.new(Grape::API) do
3597
+ api1 = Class.new(described_class) do
3483
3598
  version 'one', using: :header, vendor: 'test'
3484
3599
  resources :users do
3485
3600
  get :hello do
@@ -3488,7 +3603,7 @@ XML
3488
3603
  end
3489
3604
  end
3490
3605
 
3491
- api2 = Class.new(Grape::API) do
3606
+ api2 = Class.new(described_class) do
3492
3607
  version 'two', using: :header, vendor: 'test'
3493
3608
  resources :users do
3494
3609
  get :hello do
@@ -3507,7 +3622,7 @@ XML
3507
3622
  end
3508
3623
 
3509
3624
  it 'recognizes potential versions with mounted path' do
3510
- a = Class.new(Grape::API) do
3625
+ a = Class.new(described_class) do
3511
3626
  version :v1, using: :path
3512
3627
 
3513
3628
  get '/hello' do
@@ -3515,7 +3630,7 @@ XML
3515
3630
  end
3516
3631
  end
3517
3632
 
3518
- b = Class.new(Grape::API) do
3633
+ b = Class.new(described_class) do
3519
3634
  version :v1, using: :path
3520
3635
 
3521
3636
  get '/world' do
@@ -3535,11 +3650,11 @@ XML
3535
3650
 
3536
3651
  context 'when mounting class extends a subclass of Grape::API' do
3537
3652
  it 'mounts APIs with the same superclass' do
3538
- base_api = Class.new(Grape::API)
3653
+ base_api = Class.new(described_class)
3539
3654
  a = Class.new(base_api)
3540
3655
  b = Class.new(base_api)
3541
3656
 
3542
- expect { a.mount b }.to_not raise_error
3657
+ expect { a.mount b }.not_to raise_error
3543
3658
  end
3544
3659
  end
3545
3660
 
@@ -3549,6 +3664,7 @@ XML
3549
3664
  def self.included(base)
3550
3665
  base.extend(ClassMethods)
3551
3666
  end
3667
+
3552
3668
  module ClassMethods
3553
3669
  def my_method
3554
3670
  @test = true
@@ -3557,22 +3673,22 @@ XML
3557
3673
  end
3558
3674
  end
3559
3675
 
3560
- it 'should correctly include module in nested mount' do
3676
+ it 'correctlies include module in nested mount' do
3561
3677
  module_to_include = included_module
3562
- v1 = Class.new(Grape::API) do
3678
+ v1 = Class.new(described_class) do
3563
3679
  version :v1, using: :path
3564
3680
  include module_to_include
3565
3681
  my_method
3566
3682
  end
3567
- v2 = Class.new(Grape::API) do
3683
+ v2 = Class.new(described_class) do
3568
3684
  version :v2, using: :path
3569
3685
  end
3570
- segment_base = Class.new(Grape::API) do
3686
+ segment_base = Class.new(described_class) do
3571
3687
  mount v1
3572
3688
  mount v2
3573
3689
  end
3574
3690
 
3575
- Class.new(Grape::API) do
3691
+ Class.new(described_class) do
3576
3692
  mount segment_base
3577
3693
  end
3578
3694
 
@@ -3594,7 +3710,7 @@ XML
3594
3710
  it 'sets the instance' do
3595
3711
  expect(subject.instance).to be_nil
3596
3712
  subject.compile
3597
- expect(subject.instance).to be_kind_of(subject.base_instance)
3713
+ expect(subject.instance).to be_a(subject.base_instance)
3598
3714
  end
3599
3715
  end
3600
3716
 
@@ -3607,7 +3723,7 @@ XML
3607
3723
  end
3608
3724
 
3609
3725
  describe '.endpoint' do
3610
- before(:each) do
3726
+ before do
3611
3727
  subject.format :json
3612
3728
  subject.get '/endpoint/options' do
3613
3729
  {
@@ -3616,6 +3732,7 @@ XML
3616
3732
  }
3617
3733
  end
3618
3734
  end
3735
+
3619
3736
  it 'path' do
3620
3737
  get '/endpoint/options'
3621
3738
  options = ::Grape::Json.load(last_response.body)
@@ -3627,7 +3744,7 @@ XML
3627
3744
 
3628
3745
  describe '.route' do
3629
3746
  context 'plain' do
3630
- before(:each) do
3747
+ before do
3631
3748
  subject.get '/' do
3632
3749
  route.path
3633
3750
  end
@@ -3635,6 +3752,7 @@ XML
3635
3752
  route.path
3636
3753
  end
3637
3754
  end
3755
+
3638
3756
  it 'provides access to route info' do
3639
3757
  get '/'
3640
3758
  expect(last_response.body).to eq('/(.:format)')
@@ -3642,8 +3760,9 @@ XML
3642
3760
  expect(last_response.body).to eq('/path(.:format)')
3643
3761
  end
3644
3762
  end
3763
+
3645
3764
  context 'with desc' do
3646
- before(:each) do
3765
+ before do
3647
3766
  subject.desc 'returns description'
3648
3767
  subject.get '/description' do
3649
3768
  route.description
@@ -3653,82 +3772,98 @@ XML
3653
3772
  route.params[params[:id]]
3654
3773
  end
3655
3774
  end
3775
+
3656
3776
  it 'returns route description' do
3657
3777
  get '/description'
3658
3778
  expect(last_response.body).to eq('returns description')
3659
3779
  end
3780
+
3660
3781
  it 'returns route parameters' do
3661
3782
  get '/params/x'
3662
3783
  expect(last_response.body).to eq('y')
3663
3784
  end
3664
3785
  end
3665
3786
  end
3787
+
3666
3788
  describe '.format' do
3667
3789
  context ':txt' do
3668
- before(:each) do
3790
+ before do
3669
3791
  subject.format :txt
3670
3792
  subject.content_type :json, 'application/json'
3671
3793
  subject.get '/meaning_of_life' do
3672
3794
  { meaning_of_life: 42 }
3673
3795
  end
3674
3796
  end
3797
+
3675
3798
  it 'forces txt without an extension' do
3676
3799
  get '/meaning_of_life'
3677
3800
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3678
3801
  end
3802
+
3679
3803
  it 'does not force txt with an extension' do
3680
3804
  get '/meaning_of_life.json'
3681
3805
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
3682
3806
  end
3807
+
3683
3808
  it 'forces txt from a non-accepting header' do
3684
3809
  get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
3685
3810
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3686
3811
  end
3687
3812
  end
3813
+
3688
3814
  context ':txt only' do
3689
- before(:each) do
3815
+ before do
3690
3816
  subject.format :txt
3691
3817
  subject.get '/meaning_of_life' do
3692
3818
  { meaning_of_life: 42 }
3693
3819
  end
3694
3820
  end
3821
+
3695
3822
  it 'forces txt without an extension' do
3696
3823
  get '/meaning_of_life'
3697
3824
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3698
3825
  end
3826
+
3699
3827
  it 'accepts specified extension' do
3700
3828
  get '/meaning_of_life.txt'
3701
3829
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3702
3830
  end
3831
+
3703
3832
  it 'does not accept extensions other than specified' do
3704
3833
  get '/meaning_of_life.json'
3705
3834
  expect(last_response.status).to eq(404)
3706
3835
  end
3836
+
3707
3837
  it 'forces txt from a non-accepting header' do
3708
3838
  get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'application/json'
3709
3839
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3710
3840
  end
3711
3841
  end
3842
+
3712
3843
  context ':json' do
3713
- before(:each) do
3844
+ before do
3714
3845
  subject.format :json
3715
3846
  subject.content_type :txt, 'text/plain'
3716
3847
  subject.get '/meaning_of_life' do
3717
3848
  { meaning_of_life: 42 }
3718
3849
  end
3719
3850
  end
3851
+
3720
3852
  it 'forces json without an extension' do
3721
3853
  get '/meaning_of_life'
3722
3854
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
3723
3855
  end
3856
+
3724
3857
  it 'does not force json with an extension' do
3725
3858
  get '/meaning_of_life.txt'
3726
3859
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3727
3860
  end
3861
+
3728
3862
  it 'forces json from a non-accepting header' do
3729
3863
  get '/meaning_of_life', {}, 'HTTP_ACCEPT' => 'text/html'
3730
3864
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_json)
3731
3865
  end
3866
+
3732
3867
  it 'can be overwritten with an explicit content type' do
3733
3868
  subject.get '/meaning_of_life_with_content_type' do
3734
3869
  content_type 'text/plain'
@@ -3737,6 +3872,7 @@ XML
3737
3872
  get '/meaning_of_life_with_content_type'
3738
3873
  expect(last_response.body).to eq({ meaning_of_life: 42 }.to_s)
3739
3874
  end
3875
+
3740
3876
  it 'raised :error from middleware' do
3741
3877
  middleware = Class.new(Grape::Middleware::Base) do
3742
3878
  def before
@@ -3751,6 +3887,7 @@ XML
3751
3887
  expect(last_response.body).to eq({ error: 'Unauthorized' }.to_json)
3752
3888
  end
3753
3889
  end
3890
+
3754
3891
  context ':serializable_hash' do
3755
3892
  class SerializableHashExample
3756
3893
  def serializable_hash
@@ -3758,9 +3895,10 @@ XML
3758
3895
  end
3759
3896
  end
3760
3897
 
3761
- before(:each) do
3898
+ before do
3762
3899
  subject.format :serializable_hash
3763
3900
  end
3901
+
3764
3902
  it 'instance' do
3765
3903
  subject.get '/example' do
3766
3904
  SerializableHashExample.new
@@ -3768,6 +3906,7 @@ XML
3768
3906
  get '/example'
3769
3907
  expect(last_response.body).to eq('{"abc":"def"}')
3770
3908
  end
3909
+
3771
3910
  it 'root' do
3772
3911
  subject.get '/example' do
3773
3912
  { 'root' => SerializableHashExample.new }
@@ -3775,6 +3914,7 @@ XML
3775
3914
  get '/example'
3776
3915
  expect(last_response.body).to eq('{"root":{"abc":"def"}}')
3777
3916
  end
3917
+
3778
3918
  it 'array' do
3779
3919
  subject.get '/examples' do
3780
3920
  [SerializableHashExample.new, SerializableHashExample.new]
@@ -3783,23 +3923,26 @@ XML
3783
3923
  expect(last_response.body).to eq('[{"abc":"def"},{"abc":"def"}]')
3784
3924
  end
3785
3925
  end
3926
+
3786
3927
  context ':xml' do
3787
- before(:each) do
3928
+ before do
3788
3929
  subject.format :xml
3789
3930
  end
3931
+
3790
3932
  it 'string' do
3791
3933
  subject.get '/example' do
3792
3934
  'example'
3793
3935
  end
3794
3936
  get '/example'
3795
3937
  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
3938
+ expect(last_response.body).to eq <<~XML
3939
+ <?xml version="1.0" encoding="UTF-8"?>
3940
+ <error>
3941
+ <message>cannot convert String to xml</message>
3942
+ </error>
3943
+ XML
3802
3944
  end
3945
+
3803
3946
  it 'hash' do
3804
3947
  subject.get '/example' do
3805
3948
  {
@@ -3809,28 +3952,30 @@ XML
3809
3952
  end
3810
3953
  get '/example'
3811
3954
  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
3955
+ expect(last_response.body).to eq <<~XML
3956
+ <?xml version="1.0" encoding="UTF-8"?>
3957
+ <hash>
3958
+ <example1>example1</example1>
3959
+ <example2>example2</example2>
3960
+ </hash>
3961
+ XML
3819
3962
  end
3963
+
3820
3964
  it 'array' do
3821
3965
  subject.get '/example' do
3822
3966
  %w[example1 example2]
3823
3967
  end
3824
3968
  get '/example'
3825
3969
  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
3970
+ expect(last_response.body).to eq <<~XML
3971
+ <?xml version="1.0" encoding="UTF-8"?>
3972
+ <strings type="array">
3973
+ <string>example1</string>
3974
+ <string>example2</string>
3975
+ </strings>
3976
+ XML
3833
3977
  end
3978
+
3834
3979
  it 'raised :error from middleware' do
3835
3980
  middleware = Class.new(Grape::Middleware::Base) do
3836
3981
  def before
@@ -3842,12 +3987,12 @@ XML
3842
3987
  end
3843
3988
  get '/'
3844
3989
  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
3990
+ expect(last_response.body).to eq <<~XML
3991
+ <?xml version="1.0" encoding="UTF-8"?>
3992
+ <error>
3993
+ <message>Unauthorized</message>
3994
+ </error>
3995
+ XML
3851
3996
  end
3852
3997
  end
3853
3998
  end
@@ -3892,12 +4037,12 @@ XML
3892
4037
 
3893
4038
  context 'catch-all' do
3894
4039
  before do
3895
- api1 = Class.new(Grape::API)
4040
+ api1 = Class.new(described_class)
3896
4041
  api1.version 'v1', using: :path
3897
4042
  api1.get 'hello' do
3898
4043
  'v1'
3899
4044
  end
3900
- api2 = Class.new(Grape::API)
4045
+ api2 = Class.new(described_class)
3901
4046
  api2.version 'v2', using: :path
3902
4047
  api2.get 'hello' do
3903
4048
  'v2'
@@ -3905,6 +4050,7 @@ XML
3905
4050
  subject.mount api1
3906
4051
  subject.mount api2
3907
4052
  end
4053
+
3908
4054
  [true, false].each do |anchor|
3909
4055
  it "anchor=#{anchor}" do
3910
4056
  subject.route :any, '*path', anchor: anchor do
@@ -3937,6 +4083,7 @@ XML
3937
4083
  expect(last_response.status).to eq(404)
3938
4084
  expect(last_response.headers['X-Cascade']).to eq('pass')
3939
4085
  end
4086
+
3940
4087
  it 'does not cascade' do
3941
4088
  subject.version 'v2', using: :path, cascade: false
3942
4089
  get '/v2/hello'
@@ -3944,6 +4091,7 @@ XML
3944
4091
  expect(last_response.headers.keys).not_to include 'X-Cascade'
3945
4092
  end
3946
4093
  end
4094
+
3947
4095
  context 'via endpoint' do
3948
4096
  it 'cascades' do
3949
4097
  subject.cascade true
@@ -3951,6 +4099,7 @@ XML
3951
4099
  expect(last_response.status).to eq(404)
3952
4100
  expect(last_response.headers['X-Cascade']).to eq('pass')
3953
4101
  end
4102
+
3954
4103
  it 'does not cascade' do
3955
4104
  subject.cascade false
3956
4105
  get '/hello'
@@ -3993,6 +4142,20 @@ XML
3993
4142
  end
3994
4143
  end
3995
4144
 
4145
+ context 'with non-UTF-8 characters in specified format' do
4146
+ it 'converts the characters' do
4147
+ subject.format :json
4148
+ subject.content_type :json, 'application/json'
4149
+ subject.get '/something' do
4150
+ 'foo'
4151
+ end
4152
+ get '/something?format=%0A%0B%BF'
4153
+ expect(last_response.status).to eq(406)
4154
+ message = "The requested format '\n\u000b\357\277\275' is not supported."
4155
+ expect(last_response.body).to eq({ error: message }.to_json)
4156
+ end
4157
+ end
4158
+
3996
4159
  context 'body' do
3997
4160
  context 'false' do
3998
4161
  before do
@@ -4000,12 +4163,14 @@ XML
4000
4163
  body false
4001
4164
  end
4002
4165
  end
4166
+
4003
4167
  it 'returns blank body' do
4004
4168
  get '/blank'
4005
4169
  expect(last_response.status).to eq(204)
4006
4170
  expect(last_response.body).to be_blank
4007
4171
  end
4008
4172
  end
4173
+
4009
4174
  context 'plain text' do
4010
4175
  before do
4011
4176
  subject.get '/text' do
@@ -4014,6 +4179,7 @@ XML
4014
4179
  'ignored'
4015
4180
  end
4016
4181
  end
4182
+
4017
4183
  it 'returns blank body' do
4018
4184
  get '/text'
4019
4185
  expect(last_response.status).to eq(200)
@@ -4023,7 +4189,7 @@ XML
4023
4189
  end
4024
4190
 
4025
4191
  describe 'normal class methods' do
4026
- subject(:grape_api) { Class.new(Grape::API) }
4192
+ subject(:grape_api) { Class.new(described_class) }
4027
4193
 
4028
4194
  before do
4029
4195
  stub_const('MyAPI', grape_api)
@@ -4040,10 +4206,54 @@ XML
4040
4206
  end
4041
4207
  end
4042
4208
 
4209
+ describe '.inherited' do
4210
+ context 'overriding within class' do
4211
+ let(:root_api) do
4212
+ Class.new(described_class) do
4213
+ @bar = 'Hello, world'
4214
+
4215
+ def self.inherited(child_api)
4216
+ super
4217
+ child_api.instance_variable_set(:@foo, @bar.dup)
4218
+ end
4219
+ end
4220
+ end
4221
+
4222
+ let(:child_api) { Class.new(root_api) }
4223
+
4224
+ it 'allows overriding the hook' do
4225
+ expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
4226
+ end
4227
+ end
4228
+
4229
+ context 'overriding via composition' do
4230
+ module Inherited
4231
+ def inherited(api)
4232
+ super
4233
+ api.instance_variable_set(:@foo, @bar.dup)
4234
+ end
4235
+ end
4236
+
4237
+ let(:root_api) do
4238
+ Class.new(described_class) do
4239
+ @bar = 'Hello, world'
4240
+ extend Inherited
4241
+ end
4242
+ end
4243
+
4244
+ let(:child_api) { Class.new(root_api) }
4245
+
4246
+ it 'allows overriding the hook' do
4247
+ expect(child_api.instance_variable_get(:@foo)).to eq('Hello, world')
4248
+ end
4249
+ end
4250
+ end
4251
+
4043
4252
  describe 'const_missing' do
4044
- subject(:grape_api) { Class.new(Grape::API) }
4253
+ subject(:grape_api) { Class.new(described_class) }
4254
+
4045
4255
  let(:mounted) do
4046
- Class.new(Grape::API) do
4256
+ Class.new(described_class) do
4047
4257
  get '/missing' do
4048
4258
  SomeRandomConstant
4049
4259
  end
@@ -4058,6 +4268,12 @@ XML
4058
4268
  end
4059
4269
 
4060
4270
  describe 'custom route helpers on nested APIs' do
4271
+ subject(:grape_api) do
4272
+ Class.new(described_class) do
4273
+ version 'v1', using: :path
4274
+ end
4275
+ end
4276
+
4061
4277
  let(:shared_api_module) do
4062
4278
  Module.new do
4063
4279
  # rubocop:disable Style/ExplicitBlockArgument because this causes
@@ -4091,7 +4307,7 @@ XML
4091
4307
  let(:orders_root) do
4092
4308
  shared = shared_api_definitions
4093
4309
  find = orders_find_endpoint
4094
- Class.new(Grape::API) do
4310
+ Class.new(described_class) do
4095
4311
  include shared
4096
4312
 
4097
4313
  namespace(:orders) do
@@ -4101,7 +4317,7 @@ XML
4101
4317
  end
4102
4318
  let(:orders_find_endpoint) do
4103
4319
  shared = shared_api_definitions
4104
- Class.new(Grape::API) do
4320
+ Class.new(described_class) do
4105
4321
  include shared
4106
4322
 
4107
4323
  uniqe_id_route do
@@ -4112,11 +4328,6 @@ XML
4112
4328
  end
4113
4329
  end
4114
4330
  end
4115
- subject(:grape_api) do
4116
- Class.new(Grape::API) do
4117
- version 'v1', using: :path
4118
- end
4119
- end
4120
4331
 
4121
4332
  before do
4122
4333
  Grape::API::Instance.extend(shared_api_module)