grape 1.5.2 → 1.6.2

Sign up to get free protection for your applications and to get access to all the features.
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