restify 2.1.0 → 3.0.0.rc3

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +30 -5
  4. data/lib/restify/adapter/base.rb +10 -1
  5. data/lib/restify/adapter/ethon/easy.rb +147 -0
  6. data/lib/restify/adapter/ethon/event_loop.rb +255 -0
  7. data/lib/restify/adapter/ethon/options.rb +81 -0
  8. data/lib/restify/adapter/ethon/pool.rb +43 -0
  9. data/lib/restify/adapter/ethon.rb +211 -0
  10. data/lib/restify/adapter/loop_lock.rb +124 -0
  11. data/lib/restify/adapter/telemetry.rb +7 -5
  12. data/lib/restify/adapter/typhoeus.rb +52 -23
  13. data/lib/restify/context.rb +16 -6
  14. data/lib/restify/error.rb +13 -0
  15. data/lib/restify/global.rb +9 -30
  16. data/lib/restify/logging.rb +10 -4
  17. data/lib/restify/processors/base/parsing.rb +43 -26
  18. data/lib/restify/promise.rb +194 -42
  19. data/lib/restify/registry.rb +2 -2
  20. data/lib/restify/relation.rb +27 -13
  21. data/lib/restify/request.rb +2 -2
  22. data/lib/restify/resource.rb +20 -2
  23. data/lib/restify/response.rb +39 -3
  24. data/lib/restify/timeout.rb +23 -34
  25. data/lib/restify/version.rb +3 -3
  26. data/lib/restify/webmock.rb +126 -0
  27. data/lib/restify.rb +5 -2
  28. data/spec/restify/adapter/ethon/easy_spec.rb +111 -0
  29. data/spec/restify/adapter/ethon/event_loop_spec.rb +96 -0
  30. data/spec/restify/adapter/ethon/options_spec.rb +104 -0
  31. data/spec/restify/adapter/ethon/pool_spec.rb +81 -0
  32. data/spec/restify/adapter/ethon_spec.rb +307 -0
  33. data/spec/restify/adapter/loop_lock_spec.rb +144 -0
  34. data/spec/restify/adapter/telemetry_spec.rb +61 -0
  35. data/spec/restify/adapter/typhoeus_spec.rb +26 -0
  36. data/spec/restify/cache_spec.rb +1 -3
  37. data/spec/restify/context_spec.rb +69 -0
  38. data/spec/restify/features/opentelemetry_spec.rb +2 -0
  39. data/spec/restify/features/protocols_spec.rb +28 -0
  40. data/spec/restify/features/redirects_spec.rb +45 -0
  41. data/spec/restify/features/request_errors_spec.rb +1 -1
  42. data/spec/restify/features/response_bodies_spec.rb +45 -0
  43. data/spec/restify/features/response_encoding_spec.rb +82 -0
  44. data/spec/restify/features/response_errors_spec.rb +22 -1
  45. data/spec/restify/features/webmock_spec.rb +92 -1
  46. data/spec/restify/global_spec.rb +78 -10
  47. data/spec/restify/processors/json_spec.rb +103 -0
  48. data/spec/restify/processors/msgpack_spec.rb +36 -0
  49. data/spec/restify/promise_spec.rb +288 -9
  50. data/spec/restify/relation_spec.rb +25 -6
  51. data/spec/restify/request_spec.rb +26 -0
  52. data/spec/restify/timeout_spec.rb +43 -33
  53. data/spec/spec_helper.rb +10 -6
  54. data/spec/support/stub_server.rb +4 -4
  55. metadata +32 -221
  56. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/LICENSE +0 -21
  57. data/vendor/bundle/ruby/4.0.0/bundler/gems/my-rubocop-2b861962124a/README.md +0 -23
  58. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/CHANGELOG.md +0 -561
  59. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/MIT-LICENSE +0 -20
  60. data/vendor/bundle/ruby/4.0.0/gems/activesupport-8.1.3/README.rdoc +0 -40
  61. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/CHANGELOG.md +0 -326
  62. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/LICENSE.txt +0 -202
  63. data/vendor/bundle/ruby/4.0.0/gems/addressable-2.9.0/README.md +0 -121
  64. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/LICENSE.MIT +0 -20
  65. data/vendor/bundle/ruby/4.0.0/gems/ast-2.4.3/README.YARD.md +0 -12
  66. data/vendor/bundle/ruby/4.0.0/gems/base64-0.3.0/README.md +0 -48
  67. data/vendor/bundle/ruby/4.0.0/gems/bigdecimal-4.1.2/LICENSE +0 -56
  68. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/CHANGELOG.md +0 -615
  69. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/LICENSE.txt +0 -21
  70. data/vendor/bundle/ruby/4.0.0/gems/concurrent-ruby-1.3.7/README.md +0 -409
  71. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/LICENSE +0 -20
  72. data/vendor/bundle/ruby/4.0.0/gems/connection_pool-3.0.2/README.md +0 -182
  73. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/LICENSE +0 -20
  74. data/vendor/bundle/ruby/4.0.0/gems/crack-1.0.1/README.md +0 -43
  75. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/CHANGELOG.md +0 -518
  76. data/vendor/bundle/ruby/4.0.0/gems/diff-lcs-1.6.2/README.md +0 -92
  77. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/LICENSE +0 -21
  78. data/vendor/bundle/ruby/4.0.0/gems/docile-1.4.1/README.md +0 -409
  79. data/vendor/bundle/ruby/4.0.0/gems/drb-2.2.3/LICENSE.txt +0 -22
  80. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/CHANGELOG.md +0 -417
  81. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/LICENSE +0 -20
  82. data/vendor/bundle/ruby/4.0.0/gems/ethon-0.18.0/README.md +0 -118
  83. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/CHANGELOG.md +0 -507
  84. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE +0 -24
  85. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/LICENSE.SPECS +0 -22
  86. data/vendor/bundle/ruby/4.0.0/gems/ffi-1.17.4-x86_64-linux-gnu/README.md +0 -138
  87. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/LICENSE +0 -19
  88. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/README.md +0 -323
  89. data/vendor/bundle/ruby/4.0.0/gems/hashdiff-1.2.1/changelog.md +0 -127
  90. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/LICENSE.txt +0 -16
  91. data/vendor/bundle/ruby/4.0.0/gems/hitimes-3.2.0/README.md +0 -187
  92. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/MIT-LICENSE +0 -20
  93. data/vendor/bundle/ruby/4.0.0/gems/i18n-1.15.2/README.md +0 -139
  94. data/vendor/bundle/ruby/4.0.0/gems/json-2.19.9/README.md +0 -310
  95. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/LICENSE.txt +0 -21
  96. data/vendor/bundle/ruby/4.0.0/gems/language_server-protocol-3.17.0.5/README.md +0 -88
  97. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/CHANGELOG.md +0 -15
  98. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/LICENSE.txt +0 -21
  99. data/vendor/bundle/ruby/4.0.0/gems/lint_roller-1.1.0/README.md +0 -173
  100. data/vendor/bundle/ruby/4.0.0/gems/little-plugger-1.1.4/README.rdoc +0 -53
  101. data/vendor/bundle/ruby/4.0.0/gems/logger-1.7.0/README.md +0 -104
  102. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/LICENSE +0 -22
  103. data/vendor/bundle/ruby/4.0.0/gems/logging-2.4.0/README.md +0 -140
  104. data/vendor/bundle/ruby/4.0.0/gems/minitest-6.0.6/README.rdoc +0 -763
  105. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/ChangeLog +0 -383
  106. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/LICENSE +0 -177
  107. data/vendor/bundle/ruby/4.0.0/gems/msgpack-1.8.3/README.md +0 -302
  108. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/LICENSE.md +0 -20
  109. data/vendor/bundle/ruby/4.0.0/gems/multi_json-1.21.1/README.md +0 -281
  110. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/LICENSE +0 -37
  111. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/ext/libev/README +0 -59
  112. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/license.md +0 -80
  113. data/vendor/bundle/ruby/4.0.0/gems/nio4r-2.7.5/readme.md +0 -91
  114. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/CHANGELOG.md +0 -219
  115. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/LICENSE +0 -201
  116. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-api-1.10.0/README.md +0 -68
  117. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/CHANGELOG.md +0 -110
  118. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/LICENSE +0 -201
  119. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-common-0.25.0/README.md +0 -62
  120. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/CHANGELOG.md +0 -97
  121. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/LICENSE +0 -201
  122. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-base-0.26.1/README.md +0 -155
  123. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/CHANGELOG.md +0 -192
  124. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/LICENSE +0 -201
  125. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-instrumentation-ethon-0.29.0/README.md +0 -66
  126. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/CHANGELOG.md +0 -31
  127. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/LICENSE +0 -201
  128. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-registry-0.6.0/README.md +0 -40
  129. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/CHANGELOG.md +0 -302
  130. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/LICENSE +0 -201
  131. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-sdk-1.12.0/README.md +0 -101
  132. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/CHANGELOG.md +0 -65
  133. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/LICENSE +0 -201
  134. data/vendor/bundle/ruby/4.0.0/gems/opentelemetry-semantic_conventions-1.41.0/README.md +0 -115
  135. data/vendor/bundle/ruby/4.0.0/gems/parallel-2.1.0/MIT-LICENSE.txt +0 -20
  136. data/vendor/bundle/ruby/4.0.0/gems/parser-3.3.11.1/LICENSE.txt +0 -26
  137. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/CHANGELOG.md +0 -786
  138. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/LICENSE.md +0 -7
  139. data/vendor/bundle/ruby/4.0.0/gems/prism-1.9.0/README.md +0 -143
  140. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/CHANGELOG.md +0 -649
  141. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/LICENSE.txt +0 -22
  142. data/vendor/bundle/ruby/4.0.0/gems/public_suffix-7.0.5/README.md +0 -231
  143. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/LICENSE +0 -29
  144. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/README.md +0 -484
  145. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/README.md +0 -9
  146. data/vendor/bundle/ruby/4.0.0/gems/puma-8.0.2/docs/jungle/rc.d/README.md +0 -74
  147. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/ChangeLog +0 -846
  148. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.ja.rdoc +0 -58
  149. data/vendor/bundle/ruby/4.0.0/gems/racc-1.8.1/README.rdoc +0 -60
  150. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/CHANGELOG.md +0 -1346
  151. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/MIT-LICENSE +0 -20
  152. data/vendor/bundle/ruby/4.0.0/gems/rack-3.2.6/README.md +0 -384
  153. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/Changelog.md +0 -101
  154. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/LICENSE +0 -20
  155. data/vendor/bundle/ruby/4.0.0/gems/rainbow-3.1.1/README.markdown +0 -227
  156. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/MIT-LICENSE +0 -21
  157. data/vendor/bundle/ruby/4.0.0/gems/rake-13.4.2/README.rdoc +0 -155
  158. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/LICENSE +0 -21
  159. data/vendor/bundle/ruby/4.0.0/gems/rake-release-1.4.0/README.md +0 -107
  160. data/vendor/bundle/ruby/4.0.0/gems/regexp_parser-2.12.0/LICENSE +0 -22
  161. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/LICENSE.txt +0 -22
  162. data/vendor/bundle/ruby/4.0.0/gems/rexml-3.4.4/README.md +0 -57
  163. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/LICENSE.md +0 -27
  164. data/vendor/bundle/ruby/4.0.0/gems/rspec-3.13.2/README.md +0 -47
  165. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/Changelog.md +0 -2447
  166. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/LICENSE.md +0 -26
  167. data/vendor/bundle/ruby/4.0.0/gems/rspec-core-3.13.6/README.md +0 -389
  168. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/Changelog.md +0 -1366
  169. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/LICENSE.md +0 -25
  170. data/vendor/bundle/ruby/4.0.0/gems/rspec-expectations-3.13.5/README.md +0 -326
  171. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/Changelog.md +0 -1351
  172. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/LICENSE.md +0 -25
  173. data/vendor/bundle/ruby/4.0.0/gems/rspec-mocks-3.13.8/README.md +0 -465
  174. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/Changelog.md +0 -444
  175. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/LICENSE.md +0 -23
  176. data/vendor/bundle/ruby/4.0.0/gems/rspec-support-3.13.7/README.md +0 -40
  177. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/LICENSE.txt +0 -20
  178. data/vendor/bundle/ruby/4.0.0/gems/rubocop-1.88.0/README.md +0 -255
  179. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/LICENSE.txt +0 -20
  180. data/vendor/bundle/ruby/4.0.0/gems/rubocop-ast-1.49.1/README.md +0 -54
  181. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/CHANGELOG.md +0 -116
  182. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/MIT-LICENSE.md +0 -21
  183. data/vendor/bundle/ruby/4.0.0/gems/rubocop-capybara-2.23.0/README.md +0 -91
  184. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/CHANGELOG.md +0 -135
  185. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/MIT-LICENSE.md +0 -21
  186. data/vendor/bundle/ruby/4.0.0/gems/rubocop-factory_bot-2.28.0/README.md +0 -91
  187. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/LICENSE.txt +0 -20
  188. data/vendor/bundle/ruby/4.0.0/gems/rubocop-performance-1.26.1/README.md +0 -100
  189. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/CHANGELOG.md +0 -1136
  190. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/MIT-LICENSE.md +0 -21
  191. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec-3.10.2/README.md +0 -112
  192. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/CHANGELOG.md +0 -98
  193. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/MIT-LICENSE.md +0 -21
  194. data/vendor/bundle/ruby/4.0.0/gems/rubocop-rspec_rails-2.32.0/README.md +0 -93
  195. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/LICENSE.txt +0 -19
  196. data/vendor/bundle/ruby/4.0.0/gems/ruby-progressbar-1.13.0/README.md +0 -131
  197. data/vendor/bundle/ruby/4.0.0/gems/securerandom-0.4.1/README.md +0 -72
  198. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/CHANGELOG.md +0 -191
  199. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/LICENSE +0 -20
  200. data/vendor/bundle/ruby/4.0.0/gems/simplecov-0.22.0/README.md +0 -974
  201. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/LICENSE +0 -202
  202. data/vendor/bundle/ruby/4.0.0/gems/simplecov-cobertura-3.2.0/README.md +0 -65
  203. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/CHANGELOG.md +0 -114
  204. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/LICENSE +0 -20
  205. data/vendor/bundle/ruby/4.0.0/gems/simplecov-html-0.13.2/README.md +0 -30
  206. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/CHANGELOG.md +0 -13
  207. data/vendor/bundle/ruby/4.0.0/gems/simplecov_json_formatter-0.1.4/README.md +0 -29
  208. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/CHANGELOG.md +0 -465
  209. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/LICENSE +0 -22
  210. data/vendor/bundle/ruby/4.0.0/gems/typhoeus-1.6.0/README.md +0 -588
  211. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/LICENSE +0 -19
  212. data/vendor/bundle/ruby/4.0.0/gems/tzinfo-2.0.6/README.md +0 -406
  213. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/CHANGELOG.md +0 -299
  214. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/MIT-LICENSE.txt +0 -22
  215. data/vendor/bundle/ruby/4.0.0/gems/unicode-display_width-3.2.0/README.md +0 -194
  216. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/CHANGELOG.md +0 -202
  217. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/MIT-LICENSE.txt +0 -20
  218. data/vendor/bundle/ruby/4.0.0/gems/unicode-emoji-4.2.0/README.md +0 -205
  219. data/vendor/bundle/ruby/4.0.0/gems/uri-1.1.1/README.md +0 -55
  220. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/CHANGELOG.md +0 -2148
  221. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/LICENSE +0 -20
  222. data/vendor/bundle/ruby/4.0.0/gems/webmock-3.26.2/README.md +0 -1229
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Ethon do
6
+ describe 'completed transfers' do
7
+ subject(:value) do
8
+ Restify::Promise.create do |writer|
9
+ described_class.new.send(:complete, easy, request, writer)
10
+ end.value!
11
+ end
12
+
13
+ let(:request) { Restify::Request.new(uri: 'http://example.org/base') }
14
+ let(:easy) { instance_double(described_class::Easy, return_code: :ok, response_code: 200) }
15
+
16
+ it 'fulfills the promise with the response' do
17
+ response = instance_double(Restify::Response)
18
+ allow(easy).to receive(:response).with(request).and_return(response)
19
+
20
+ expect(value).to be response
21
+ end
22
+
23
+ it 'rejects the promise on network errors' do
24
+ allow(easy).to receive(:response).and_raise(Restify::NetworkError.new(request, 'kaboom'))
25
+
26
+ expect { value }.to raise_error Restify::NetworkError, /kaboom/
27
+ end
28
+
29
+ # Unexpected errors must reject the promise too, as the caller would
30
+ # otherwise keep waiting for it until it times out.
31
+ it 'rejects the promise on unexpected errors' do
32
+ allow(easy).to receive(:response).and_raise('kaboom')
33
+
34
+ expect { value }.to raise_error 'kaboom'
35
+ end
36
+ end
37
+
38
+ describe 'waiting on requests' do
39
+ let(:adapter) { described_class.new }
40
+
41
+ # Threads that completed a transfer, i.e. ran the event loop.
42
+ let(:completed_by) { Queue.new }
43
+
44
+ before do
45
+ stub_request(:get, 'http://stubserver/fast')
46
+ .to_return(status: 200, body: '{}')
47
+ stub_request(:get, 'http://stubserver/slow')
48
+ .to_return { sleep 0.5 and {status: 200, body: '{}'} }
49
+
50
+ allow(adapter).to receive(:complete).and_wrap_original do |m, *args|
51
+ completed_by << Thread.current
52
+ m.call(*args)
53
+ end
54
+ end
55
+
56
+ def request(path)
57
+ Restify::Request.new(uri: "http://localhost:9292/#{path}")
58
+ end
59
+
60
+ def completed_by_threads
61
+ Array.new(completed_by.size) { completed_by.pop }
62
+ end
63
+
64
+ it 'runs the event loop in the waiting thread' do
65
+ expect(adapter.call(request('fast')).value!.code).to eq 200
66
+ expect(completed_by_threads).to eq [Thread.current]
67
+ end
68
+
69
+ it 'completes requests nobody waits on in the background' do
70
+ adapter.call(request('fast'))
71
+
72
+ # Poll without waiting on the promise:
73
+ Timeout.timeout(1) { sleep 0.01 while completed_by.empty? }
74
+
75
+ expect(completed_by_threads).not_to include Thread.current
76
+ end
77
+
78
+ it 'times out without blocking other requests' do
79
+ expect { adapter.call(request('slow')).value!(0.1) }.to raise_error Timeout::Error
80
+ expect(adapter.call(request('fast')).value!.code).to eq 200
81
+ end
82
+
83
+ it 'can wait again after a timeout' do
84
+ promise = adapter.call(request('slow'))
85
+
86
+ expect { promise.value!(0.1) }.to raise_error Timeout::Error
87
+ expect(promise.value!(1).code).to eq 200
88
+ end
89
+
90
+ it 'processes requests from many threads' do
91
+ threads = Array.new(8) do
92
+ Thread.new do
93
+ Array.new(5) { adapter.call(request('fast')).value!.code }
94
+ end
95
+ end
96
+
97
+ expect(threads.flat_map(&:value)).to all eq 200
98
+ end
99
+
100
+ # Only the adapter's own promises are completed by whichever thread
101
+ # runs the event loop. Callbacks chained to them must still run in
102
+ # the thread waiting on them, and only when waiting on them.
103
+ describe 'chained callbacks' do
104
+ it 'does not run them before waiting on them' do
105
+ ran_in = Queue.new
106
+ promise = adapter.call(request('fast'))
107
+ chained = promise.then { ran_in << Thread.current }
108
+
109
+ promise.value!
110
+ expect(ran_in).to be_empty
111
+
112
+ chained.value!
113
+ expect(ran_in.pop(timeout: 1)).to eq Thread.current
114
+ end
115
+
116
+ it 'runs them in the waiting thread when another thread runs the loop' do
117
+ # Keep the loop busy in another thread, until after the fast
118
+ # request completed.
119
+ driver = Thread.new { adapter.call(request('slow')).value! }
120
+ sleep 0.1
121
+
122
+ chained = adapter.call(request('fast')).then do |response|
123
+ [Thread.current, response.code]
124
+ end
125
+
126
+ expect(chained.value!).to eq [Thread.current, 200]
127
+ expect(completed_by_threads.first).to eq driver
128
+ ensure
129
+ driver&.join
130
+ end
131
+ end
132
+
133
+ context 'when waiting from within the event loop' do
134
+ it 'does not run the loop again' do
135
+ adapter.instance_variable_get(:@events).instance_variable_get(:@lock).acquire(0.1)
136
+
137
+ expect(adapter.drive(Restify::Promise.new, Restify::Timeout.new(0.1))).to be false
138
+ end
139
+ end
140
+ end
141
+
142
+ describe 'reusing easy handles' do
143
+ let(:adapter) { described_class.new }
144
+ let(:root) { Restify.new('http://localhost:9292/echo', adapter:) }
145
+
146
+ # Easy handles of completed transfers, in order.
147
+ let(:handles) { [] }
148
+
149
+ before do
150
+ allow(adapter).to receive(:complete).and_wrap_original do |m, easy, *args|
151
+ handles << easy
152
+ m.call(easy, *args)
153
+ end
154
+ end
155
+
156
+ def echo(method, *, **)
157
+ JSON.parse(root.send(method, *, **).value!.response.body)
158
+ end
159
+
160
+ it 'reuses the handle of a completed request' do
161
+ 2.times { root.get.value! }
162
+
163
+ expect(handles[1]).to be handles[0]
164
+ end
165
+
166
+ it 'does not reuse handles of requests in progress' do
167
+ Restify::Promise.new(Array.new(2) { root.get }).value!
168
+
169
+ expect(handles[1]).not_to be handles[0]
170
+ end
171
+
172
+ # Nothing must leak from one request into the next one.
173
+ describe 'subsequent requests' do
174
+ it 'use the method of each request' do
175
+ %i[post get head put patch delete get].each do |method|
176
+ response = root.send(method).value!.response
177
+ next if method == :head
178
+
179
+ expect(JSON.parse(response.body)).to include('REQUEST_METHOD' => method.to_s.upcase)
180
+ end
181
+ end
182
+
183
+ it 'do not send a previous body' do
184
+ root.post('payload').value!
185
+
186
+ expect(echo(:get)).not_to have_key('CONTENT_LENGTH')
187
+ end
188
+
189
+ it 'do not send previous headers' do
190
+ root.get(headers: {'X-Custom' => 'yes'}).value!
191
+
192
+ expect(echo(:get)).not_to have_key('HTTP_X_CUSTOM')
193
+ end
194
+
195
+ it 'receive a body after a HEAD request' do
196
+ root.head.value!
197
+
198
+ expect(root.get.value!.response.body).not_to be_empty
199
+ end
200
+ end
201
+
202
+ describe 'request bodies' do
203
+ before { stub_request(:any, 'http://stubserver/body') }
204
+
205
+ let(:root) { Restify.new('http://localhost:9292/body', adapter:) }
206
+
207
+ %i[post put patch].each do |method|
208
+ it "sends #{method.upcase} bodies" do
209
+ root.send(method, 'payload').value!
210
+ root.send(method, 'other').value!
211
+
212
+ expect(a_request(method, 'http://stubserver/body').with(body: 'payload')).to have_been_made.once
213
+ expect(a_request(method, 'http://stubserver/body').with(body: 'other')).to have_been_made.once
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ describe 'options' do
220
+ it 'raises on unknown options' do
221
+ expect { described_class.new(options: {unknown: 1}) }.to raise_error ArgumentError, /Unknown libcurl option/
222
+ end
223
+
224
+ it 'sets options on each request' do
225
+ adapter = described_class.new(options: {useragent: 'restify-spec'})
226
+ root = Restify.new('http://localhost:9292/echo', adapter:)
227
+
228
+ 2.times do
229
+ expect(JSON.parse(root.get.value!.response.body)).to include('HTTP_USER_AGENT' => 'restify-spec')
230
+ end
231
+ end
232
+ end
233
+
234
+ describe 'after fork' do
235
+ let(:adapter) { described_class.new }
236
+ let(:connects) { Queue.new }
237
+
238
+ before do
239
+ stub_request(:get, 'http://stubserver/fast')
240
+ .to_return(status: 200, body: '{}')
241
+ stub_request(:get, 'http://stubserver/slow')
242
+ .to_return { sleep 0.5 and {status: 200, body: '{}'} }
243
+
244
+ allow(adapter).to receive(:complete).and_wrap_original do |m, easy, *args|
245
+ connects << Ethon::Curl.get_info_long(:num_connects, easy.handle)
246
+ m.call(easy, *args)
247
+ end
248
+ end
249
+
250
+ def request(path)
251
+ Restify::Request.new(uri: "http://localhost:9292/#{path}")
252
+ end
253
+
254
+ def in_child
255
+ reader, writer = IO.pipe
256
+
257
+ pid = fork do
258
+ reader.close
259
+ result = begin
260
+ yield
261
+ rescue Exception => e # rubocop:disable Lint/RescueException
262
+ e
263
+ end
264
+ writer.write(Marshal.dump(result))
265
+ ensure
266
+ # Skip at exit handlers of the parent
267
+ exit!(0)
268
+ end
269
+
270
+ writer.close
271
+ Marshal.load(reader.read) # rubocop:disable Security/MarshalLoad
272
+ ensure
273
+ reader&.close
274
+ Process.wait(pid) if pid
275
+ end
276
+
277
+ it 'does not use connections of the parent process' do
278
+ # The parent process pools and reuses its connection.
279
+ 2.times { adapter.call(request('fast')).value! }
280
+ expect(Array.new(2) { connects.pop(timeout: 1) }).to eq [1, 0]
281
+
282
+ result = in_child do
283
+ code = adapter.call(request('fast')).value!.code
284
+ [code, connects.pop(timeout: 1)]
285
+ end
286
+
287
+ # A new connection, not the one pooled by the parent process
288
+ expect(result).to eq [200, 1]
289
+
290
+ # The parent process can still use its connections
291
+ expect(adapter.call(request('fast')).value!.code).to eq 200
292
+ end
293
+
294
+ it 'rejects requests started before the fork in the child process' do
295
+ promise = adapter.call(request('slow'))
296
+
297
+ result = in_child do
298
+ promise.value!
299
+ rescue Restify::NetworkError => e
300
+ e.message
301
+ end
302
+
303
+ expect(result).to include 'before fork'
304
+ expect(promise.value!.code).to eq 200
305
+ end
306
+ end
307
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::LoopLock do
6
+ subject(:lock) { described_class.new { interrupts << Thread.current } }
7
+
8
+ let(:interrupts) { Queue.new }
9
+ let(:threads) { [] }
10
+
11
+ after { threads.each(&:kill).each(&:join) }
12
+
13
+ # Run the block in another thread, and wait until it has returned.
14
+ # The thread is kept alive until the example finished.
15
+ def other(&)
16
+ done = Queue.new
17
+ threads << Thread.new do
18
+ yield
19
+ done << true
20
+ sleep
21
+ end
22
+ expect(done.pop(timeout: 1)).to be true
23
+ end
24
+
25
+ def elapsed
26
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
27
+ yield
28
+ Process.clock_gettime(Process::CLOCK_MONOTONIC) - start
29
+ end
30
+
31
+ describe '#acquire' do
32
+ it 'acquires a free loop' do
33
+ expect(lock.acquire(1)).to be true
34
+ expect(lock).to be_owned
35
+ end
36
+
37
+ it 'does not acquire the loop when done' do
38
+ expect(lock.acquire(1) { true }).to be false
39
+ expect(lock).not_to be_owned
40
+ end
41
+
42
+ it 'times out while another thread holds the loop' do
43
+ other { lock.acquire(1) }
44
+
45
+ expect(elapsed { expect(lock.acquire(0.1)).to be false }).to be >= 0.1
46
+ expect(lock).not_to be_owned
47
+ end
48
+
49
+ it 'waits for another thread to release the loop' do
50
+ acquired = Queue.new
51
+ released = false
52
+ threads << Thread.new do
53
+ lock.acquire(1)
54
+ acquired << true
55
+ sleep 0.1
56
+ released = true
57
+ lock.release
58
+ end
59
+ expect(acquired.pop(timeout: 1)).to be true
60
+
61
+ expect(lock.acquire(1)).to be true
62
+ expect(released).to be true
63
+ end
64
+
65
+ it 'stops waiting when done after being notified' do
66
+ done = false
67
+ other { lock.acquire(1) }
68
+ threads << Thread.new do
69
+ sleep 0.1
70
+ done = true
71
+ lock.notify
72
+ end
73
+
74
+ expect(elapsed { expect(lock.acquire(1) { done }).to be false }).to be < 0.5
75
+ end
76
+
77
+ it 'treats a dead thread as not holding the loop' do
78
+ Thread.new { lock.acquire(1) }.join
79
+
80
+ expect(lock.acquire(0.1)).to be true
81
+ end
82
+ end
83
+
84
+ describe '#release' do
85
+ it 'lets a waiting thread acquire the loop' do
86
+ lock.acquire(1)
87
+ waiter = Thread.new { lock.acquire(1) }
88
+ sleep 0.05
89
+
90
+ lock.release
91
+
92
+ expect(waiter.join(1)&.value).to be true
93
+ expect(lock).not_to be_owned
94
+ end
95
+ end
96
+
97
+ describe 'with a background thread' do
98
+ let(:background) { Queue.new }
99
+
100
+ # A background thread running the loop until interrupted, like an
101
+ # event loop waiting for sockets. Blocks on `interrupts` on purpose,
102
+ # and is killed after each example.
103
+ def start_background
104
+ threads << Thread.new do
105
+ loop do
106
+ lock.acquire_background
107
+ background << :acquired
108
+ interrupts.pop
109
+ lock.release
110
+ end
111
+ end
112
+
113
+ expect(background.pop(timeout: 1)).to eq :acquired
114
+ end
115
+
116
+ it 'interrupts the background thread to release the loop' do
117
+ start_background
118
+
119
+ expect(lock.acquire(1)).to be true
120
+ expect(background).to be_empty
121
+ end
122
+
123
+ it 'blocks the background thread while a thread holds the loop' do
124
+ start_background
125
+ lock.acquire(1)
126
+
127
+ sleep 0.1
128
+ expect(background).to be_empty
129
+
130
+ lock.release
131
+ expect(background.pop(timeout: 1)).to eq :acquired
132
+ end
133
+ end
134
+
135
+ describe '#reset!' do
136
+ it 'forgets threads holding the loop' do
137
+ other { lock.acquire(1) }
138
+
139
+ lock.reset!
140
+
141
+ expect(lock.acquire(0.1)).to be true
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Telemetry do
6
+ subject(:call) { adapter.call(request).value! }
7
+
8
+ let(:adapter) do
9
+ Class.new(Restify::Adapter::Base) do
10
+ def call_native(request, writer)
11
+ writer.fulfill(Restify::Response.new(request, request.uri, 200, {}, ''))
12
+ end
13
+ end.new
14
+ end
15
+
16
+ let(:request) { Restify::Request.new(uri:) }
17
+ let(:span) { OTLE_EXPORTER.finished_spans.last }
18
+
19
+ before { call }
20
+
21
+ context 'with an explicit port' do
22
+ let(:uri) { Addressable::URI.parse('https://example.org:8443/resources') }
23
+
24
+ it 'records the given port' do
25
+ expect(span.name).to eq 'GET https://example.org:8443'
26
+ expect(span.attributes).to eq({
27
+ 'http.request.method' => 'GET',
28
+ 'http.response.status_code' => 200,
29
+ 'server.address' => 'example.org',
30
+ 'server.port' => 8443,
31
+ 'url.full' => 'https://example.org:8443/resources',
32
+ 'url.scheme' => 'https',
33
+ })
34
+ end
35
+ end
36
+
37
+ context 'without a port' do
38
+ let(:uri) { Addressable::URI.parse('https://example.org/resources') }
39
+
40
+ it 'records the default port of the scheme' do
41
+ expect(span.name).to eq 'GET https://example.org:443'
42
+ expect(span.attributes).to include(
43
+ 'server.port' => 443,
44
+ 'url.full' => 'https://example.org/resources',
45
+ )
46
+ end
47
+ end
48
+
49
+ context 'with a URI given as a string' do
50
+ let(:uri) { 'http://example.org/resources' }
51
+
52
+ it 'parses it' do
53
+ expect(span.name).to eq 'GET http://example.org:80'
54
+ expect(span.attributes).to include(
55
+ 'server.address' => 'example.org',
56
+ 'server.port' => 80,
57
+ 'url.full' => 'http://example.org/resources',
58
+ )
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify::Adapter::Typhoeus do
6
+ let(:request) { Restify::Request.new(uri: 'http://localhost:9292/base') }
7
+
8
+ before do
9
+ stub_request(:get, 'http://stubserver/base').to_return(status: 200, body: '{}')
10
+ end
11
+
12
+ describe 'sync' do
13
+ let(:adapter) { described_class.new(sync: true) }
14
+
15
+ it 'runs requests from many threads' do
16
+ threads = Array.new(4) do
17
+ Thread.new { Array.new(10) { adapter.call(request).value!.code } }
18
+ end
19
+
20
+ # Do not hang the suite on a deadlock.
21
+ expect(threads.map {|thread| thread.join(5)&.value }).to all eq Array.new(10, 200)
22
+ ensure
23
+ threads&.each(&:kill)
24
+ end
25
+ end
26
+ end
@@ -1,13 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'active_support'
5
- require 'active_support/cache'
6
4
 
7
5
  describe Restify::Cache do
8
6
  subject(:cache) { described_class.new(store) }
9
7
 
10
- let(:store) { instance_double(ActiveSupport::Cache::Store) }
8
+ let(:store) { Object.new }
11
9
 
12
10
  describe '#call' do
13
11
  let(:request) { instance_double(Restify::Request) }
@@ -71,4 +71,73 @@ describe Restify::Context do
71
71
  it_behaves_like 'serialization'
72
72
  end
73
73
  end
74
+
75
+ describe '#join' do
76
+ let(:uri) { 'http://localhost/base/' }
77
+
78
+ it 'resolves relative URIs' do
79
+ expect(context.join('../other').to_s).to eq 'http://localhost/other'
80
+ end
81
+
82
+ it 'returns absolute URIs as they are' do
83
+ absolute = Addressable::URI.parse('http://example.org/path')
84
+
85
+ expect(context.join(absolute)).to be absolute
86
+ end
87
+ end
88
+
89
+ describe '#request' do
90
+ subject(:request) { context.request(:get, '/').value! }
91
+
92
+ let(:adapter) do
93
+ response = Restify::Response.new(nil, Addressable::URI.parse(uri), 200, {}, 'body')
94
+
95
+ instance_double(Restify::Adapter::Base).tap do |adapter|
96
+ allow(adapter).to receive(:call).and_return(Restify::Promise.fulfilled(response))
97
+ end
98
+ end
99
+
100
+ let(:cache) do
101
+ Object.new.tap do |cache|
102
+ def cache.call(request)
103
+ yield(request)
104
+ end
105
+ end
106
+ end
107
+
108
+ let(:kwargs) { {adapter:} }
109
+
110
+ it 'calls the adapter without a cache' do
111
+ expect(adapter).to receive(:call).with(Restify::Request)
112
+
113
+ request
114
+ end
115
+
116
+ context 'with a cache' do
117
+ let(:kwargs) { {adapter:, cache:} }
118
+
119
+ it 'lets the cache call the adapter' do
120
+ expect(cache).to receive(:call).with(Restify::Request).and_call_original
121
+ expect(adapter).to receive(:call).with(Restify::Request)
122
+
123
+ request
124
+ end
125
+ end
126
+
127
+ context 'with a global cache' do
128
+ around do |example|
129
+ Restify.cache = cache
130
+ example.run
131
+ ensure
132
+ Restify.cache = nil
133
+ end
134
+
135
+ it 'lets the cache call the adapter' do
136
+ expect(cache).to receive(:call).with(Restify::Request).and_call_original
137
+ expect(adapter).to receive(:call).with(Restify::Request)
138
+
139
+ request
140
+ end
141
+ end
142
+ end
74
143
  end
@@ -39,6 +39,8 @@ describe Restify do
39
39
  # have the Restify span as a parent.
40
40
  spans[0].tap do |span|
41
41
  expect(span.instrumentation_scope.name).to include('Ethon')
42
+ expect(span.name).to eq 'GET'
43
+ expect(span.attributes).to include('http.request.method' => 'GET')
42
44
  expect(span.parent_span_id).to eq spans[1].span_id
43
45
  end
44
46
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Restify do
6
+ # Restify follows relations from URLs in server responses, and libcurl
7
+ # supports far more than HTTP. A server must therefore never be able
8
+ # to make it access e.g. a `file://` or `gopher://` URL.
9
+ describe 'Protocol restrictions' do
10
+ subject(:request) { Restify.new(uri).get.value! }
11
+
12
+ context 'with a file URI' do
13
+ let(:uri) { "file://#{File.expand_path(__FILE__)}" }
14
+
15
+ it 'refuses the request' do
16
+ expect { request }.to raise_error Restify::NetworkError, /protocol/i
17
+ end
18
+ end
19
+
20
+ context 'with a gopher URI' do
21
+ let(:uri) { 'gopher://localhost:70/1' }
22
+
23
+ it 'refuses the request' do
24
+ expect { request }.to raise_error Restify::NetworkError, /protocol/i
25
+ end
26
+ end
27
+ end
28
+ end