grape 1.5.2 → 1.6.2

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