restify 1.15.2 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -2
  3. data/README.md +23 -31
  4. data/lib/restify/adapter/base.rb +4 -0
  5. data/lib/restify/adapter/telemetry.rb +54 -0
  6. data/lib/restify/adapter/typhoeus.rb +21 -3
  7. data/lib/restify/context.rb +3 -3
  8. data/lib/restify/error.rb +2 -2
  9. data/lib/restify/link.rb +4 -4
  10. data/lib/restify/processors/base/parsing.rb +2 -21
  11. data/lib/restify/processors/base.rb +1 -1
  12. data/lib/restify/promise.rb +2 -2
  13. data/lib/restify/registry.rb +1 -1
  14. data/lib/restify/relation.rb +45 -17
  15. data/lib/restify/request.rb +6 -6
  16. data/lib/restify/timeout.rb +2 -2
  17. data/lib/restify/version.rb +3 -3
  18. data/lib/restify.rb +0 -1
  19. data/spec/restify/cache_spec.rb +16 -12
  20. data/spec/restify/context_spec.rb +8 -3
  21. data/spec/restify/error_spec.rb +13 -16
  22. data/spec/restify/features/head_requests_spec.rb +5 -4
  23. data/spec/restify/features/request_bodies_spec.rb +8 -8
  24. data/spec/restify/features/request_errors_spec.rb +2 -2
  25. data/spec/restify/features/request_headers_spec.rb +3 -6
  26. data/spec/restify/features/response_errors_spec.rb +1 -1
  27. data/spec/restify/global_spec.rb +10 -10
  28. data/spec/restify/processors/base_spec.rb +6 -7
  29. data/spec/restify/processors/json_spec.rb +21 -62
  30. data/spec/restify/processors/msgpack_spec.rb +33 -70
  31. data/spec/restify/promise_spec.rb +31 -31
  32. data/spec/restify/registry_spec.rb +5 -7
  33. data/spec/restify/relation_spec.rb +185 -7
  34. data/spec/restify/resource_spec.rb +47 -53
  35. data/spec/restify/timeout_spec.rb +3 -3
  36. data/spec/restify_spec.rb +12 -73
  37. data/spec/spec_helper.rb +11 -15
  38. metadata +33 -64
  39. data/lib/restify/adapter/em.rb +0 -134
  40. data/lib/restify/adapter/pooled_em.rb +0 -269
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c29c95a9b1c0b18f2a73b51c99da7cdddf163e1cee33c43272810f5557b1375b
4
- data.tar.gz: 95f956684154058cc6f25b7d4ffebde6706864b00c1369e91c8f8b5fcc50f0d9
3
+ metadata.gz: 85964ce70270ead0fcc3271b733611bcb97f8415e03a91b46cb402032a4c68f5
4
+ data.tar.gz: 0c99e59283131de272b9a2a1ad23589395bb89e5b8753e330f305e7ec1483685
5
5
  SHA512:
6
- metadata.gz: f420cc4d0c4ebbdade1bc1a780c24218b5eea707bd8e420c113d155d58067936464dc3d4807717de8f515ed586784c9f13476b46c66f68c8d1181501937db3a2
7
- data.tar.gz: 71da11cdf50d25abc968af02c6157c63f1aa5703c88f7ae28c3394aa028d9f038013d220283510532f7b540dffc69d0abad5fe4952c4e27df4910a4009475ae5
6
+ metadata.gz: 58bf934808c55172f28ec0673f543531c935b68e8e1f8fcf62435b49dfd7ab2e13522ef6c8b675fbfaf234be7ebfac4fdbfe05d57da4e97288b3876000a2487f
7
+ data.tar.gz: 4617aa58aa9ba400e1f28a0e5a854c5b56a560abb818432211958295e695feab6fe4bfeb0b5bd6418b231260db778804f4cf5309a34f8d1db9cb501d5f48fe6b
data/CHANGELOG.md CHANGED
@@ -5,8 +5,8 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
-
9
8
  ## Unreleased
9
+
10
10
  ---
11
11
 
12
12
  ### New
@@ -17,6 +17,30 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
17
17
 
18
18
  ### Breaks
19
19
 
20
+ ## 2.0.0 - (2025-02-14)
21
+
22
+ ---
23
+
24
+ ### New
25
+
26
+ - Support for Ruby 3.4
27
+ - Experimental support for OpenTelemetry tracing
28
+
29
+ ### Changes
30
+
31
+ - Strict keyword handling for request methods
32
+
33
+ All request methods take parameters and headers as explicit keyword arguments, not as secondary arguments anymore. Change e.g. `get({}, {headers: ...})` into `get(headers: ...)`, and `head(params)` to `head(params:)`.
34
+
35
+ `#post`, `#put`, and `#patch` still take an optional first positional data/body argument. Change `post(body, {}, {headers: ...})` to `post(body, headers: ...)`.
36
+
37
+ `#get`, `#head`, and `#delete` accept a hash as the first positional argument, which is merged with `params:`. Therefore, passing parameters as data works too: `get({id: 1})`.
38
+
39
+ ### Breaks
40
+
41
+ - Remove indifferent access methods (Hashie) from responses
42
+ - Removed `em` and `em-pooled` adapters
43
+ - Require Ruby 3.1+
20
44
 
21
45
  ## 1.15.2 - (2021-12-23)
22
46
 
@@ -26,7 +50,6 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
26
50
 
27
51
  - ActiveSupport v7.0 issues with cache module
28
52
 
29
-
30
53
  ## 1.15.1 - (2021-07-15)
31
54
 
32
55
  ---
data/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # Restify
2
2
 
3
- [![Build Status](https://travis-ci.org/jgraichen/restify.svg?branch=master)](https://travis-ci.org/jgraichen/restify)
4
- [![Code Quality](https://codebeat.co/badges/18ffe6b7-8239-493a-b5b6-be329b9f275d)](https://codebeat.co/projects/github-com-jgraichen-restify-master)
3
+ [![Gem Version](https://img.shields.io/gem/v/restify?logo=ruby)](https://rubygems.org/gems/restify)
4
+ [![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/jgraichen/restify/test.yml?logo=github)](https://github.com/jgraichen/restify/actions)
5
+ [![Code Quality](https://codebeat.co/badges/368f8033-bd76-48bc-9777-85f1d4befa94)](https://codebeat.co/projects/github-com-jgraichen-restify-main)
5
6
 
6
7
  Restify is an hypermedia REST client that does parallel, concurrent and keep-alive requests by default.
7
8
 
@@ -27,16 +28,19 @@ Links are extracted from
27
28
  * HTTP Link header
28
29
  * Github-style relations in payloads
29
30
 
30
- ### Planned features
31
+ ## Installation
31
32
 
32
- * HTTP cache
33
- * API versions via header
34
- * Content-Type and Language negotiation
35
- * Processors for JSON-HAL, etc.
33
+ Add it to your Gemfile:
36
34
 
37
- ## Installation
35
+ ```ruby
36
+ gem 'restify', '~> 2.0'
37
+ ```
38
38
 
39
- Add it to your Gemfile or install it manually: `$ gem install restify`
39
+ Or install it manually:
40
+
41
+ ```console
42
+ gem install restify
43
+ ```
40
44
 
41
45
  ## Usage
42
46
 
@@ -51,7 +55,7 @@ client = Restify.new('https://api.github.com').get.value
51
55
  # ...
52
56
  ```
53
57
 
54
- We are essentially requesting `'http://api.github.com'` via HTTP `get`. `get` is returning an `Promise`, similar to Java's `Future`. The `value` call resolves the returned `Promise` by blocking the thread until the resource is actually there. `value!` will additionally raise errors instead of returning `nil`. You can chain handlers using the `then` method. This allows you to be build a dependency chain that will be executed when the last promise is needed.
58
+ We are essentially requesting `'http://api.github.com'` via HTTP `get`. `get` is returning a `Promise`, similar to Java's `Future`. The `value` call resolves the returned `Promise` by blocking the thread until the resource is actually there. `value!` will additionally raise errors instead of returning `nil`. You can chain handlers using the `then` method. This allows you to be build a dependency chain that will be executed when the last promise is needed.
55
59
 
56
60
  As we can see GitHub returns us a field `repository_url` with a URI template. Restify automatically scans for `*_url` fields in the JSON response and exposes these as relations. It additionally scans the HTTP Header field `Link` for relations like pagination.
57
61
 
@@ -65,33 +69,21 @@ repositories = client.rel(:repository)
65
69
  This gets us the relation named `repository` that we can request now. The usual HTTP methods are available on a relation:
66
70
 
67
71
  ```ruby
68
- def get(params = {})
69
- request :get, nil, params
70
- end
71
-
72
- def delete(params = {})
73
- request :delete, nil, params
74
- end
75
-
76
- def post(data = {}, params = {})
77
- request :post, data, params
78
- end
79
-
80
- def put(data = {}, params = {})
81
- request :put, data, params
82
- end
72
+ def get(params, params:, headers:, **)
73
+ def head(params, params:, headers:, **)
74
+ def delete(params, params:, headers:, **)
83
75
 
84
- def patch(data = {}, params = {})
85
- request :patch, data, params
86
- end
76
+ def put(data = nil, params:, headers:, **)
77
+ def post(data = nil, params:, headers:, **)
78
+ def patch(data = nil, params:, headers:, **)
87
79
  ```
88
80
 
89
- URL templates can define some parameters such as `{owner}` or `{repo}`. They will be expanded from the `params` given to the HTTP method method.
81
+ URL templates can define some parameters such as `{owner}` or `{repo}`. They will be expanded from the `params` given to the HTTP method.
90
82
 
91
83
  Now send a GET request with some parameters to request a specific repository:
92
84
 
93
85
  ```ruby
94
- repo = repositories.get(owner: 'jgraichen', repo: 'restify').value
86
+ repo = repositories.get({owner: 'jgraichen', repo: 'restify'}).value
95
87
  ```
96
88
 
97
89
  Now fetch a list of commits for this repo and get this first one:
@@ -121,7 +113,7 @@ See commented example in main spec [`spec/restify_spec.rb`](https://github.com/j
121
113
 
122
114
  ## License
123
115
 
124
- Copyright (C) 2014-2018 Jan Graichen
116
+ Copyright (C) 2014-2025 Jan Graichen
125
117
 
126
118
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
127
119
 
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'restify/adapter/telemetry'
4
+
3
5
  module Restify
4
6
  module Adapter
5
7
  class Base
8
+ prepend Telemetry
9
+
6
10
  def call(request)
7
11
  Promise.create do |writer|
8
12
  call_native request, writer
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'opentelemetry'
4
+ require 'opentelemetry/common'
5
+
6
+ module Restify
7
+ module Adapter
8
+ module Telemetry
9
+ def call(request)
10
+ method = request.method.to_s.upcase
11
+ uri = URI.parse(request.uri)
12
+ name = "#{method} #{uri.scheme}://#{uri.host}:#{uri.port}"
13
+
14
+ attributes = {
15
+ 'http.request.method' => method,
16
+ 'server.address' => uri.host,
17
+ 'server.port' => uri.port,
18
+ 'url.full' => uri.to_s,
19
+ 'url.scheme' => uri.scheme,
20
+ }
21
+
22
+ span = tracer.start_span(name, attributes:, kind: :client)
23
+ OpenTelemetry::Trace.with_span(span) do
24
+ OpenTelemetry.propagation.inject(request.headers)
25
+
26
+ super.tap do |x|
27
+ x.add_observer do |_, response, err|
28
+ if response
29
+ span.set_attribute('http.response.status_code', response&.code)
30
+ span.status = OpenTelemetry::Trace::Status.error unless (100..399).cover?(response&.code)
31
+ end
32
+
33
+ span.status = OpenTelemetry::Trace::Status.error(err) if err
34
+
35
+ span.finish
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def tracer
44
+ Telemetry.tracer
45
+ end
46
+
47
+ class << self
48
+ def tracer
49
+ @tracer ||= OpenTelemetry.tracer_provider.tracer('restify', Restify::VERSION.to_s)
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  require 'typhoeus'
4
4
 
5
- ::Ethon.logger = ::Logging.logger[Ethon]
5
+ Ethon.logger = Logging.logger[Ethon]
6
6
 
7
7
  module Restify
8
8
  module Adapter
@@ -23,8 +23,26 @@ module Restify
23
23
  tcp_keepintvl: 5,
24
24
  }.freeze
25
25
 
26
+ # Patch to store easy handles in the queue, instead of requests.
27
+ # This improves compatibility with OpenTelemetry instrumentation
28
+ # for Ethon, that needs the request handle to be constructed in
29
+ # the current threading context, not the background thread were
30
+ # Typhoeus is running.
31
+ module EasyOverride
32
+ def queue(request)
33
+ request.hydra = self
34
+ queued_requests << ::Typhoeus::EasyFactory.new(request, self).get
35
+ end
36
+
37
+ def add(handle)
38
+ multi.add(handle)
39
+ end
40
+ end
41
+
26
42
  def initialize(sync: false, options: {}, **kwargs)
27
- @hydra = ::Typhoeus::Hydra.new(**kwargs)
43
+ @hydra = ::Typhoeus::Hydra.new(**kwargs)
44
+ @hydra.extend(EasyOverride)
45
+
28
46
  @mutex = Mutex.new
29
47
  @options = DEFAULT_OPTIONS.merge(options)
30
48
  @queue = Queue.new
@@ -102,7 +120,7 @@ module Restify
102
120
  def convert_headers(headers)
103
121
  return {} unless headers.respond_to?(:each_pair)
104
122
 
105
- headers.each_pair.each_with_object({}) do |header, memo|
123
+ headers.each_pair.with_object({}) do |header, memo|
106
124
  memo[header[0].upcase.tr('-', '_')] = header[1]
107
125
  end
108
126
  end
@@ -51,9 +51,9 @@ module Restify
51
51
  request = Request.new(
52
52
  headers: default_headers.merge(headers),
53
53
  **kwargs,
54
- method: method,
54
+ method:,
55
55
  uri: join(uri),
56
- data: data,
56
+ data:,
57
57
  )
58
58
 
59
59
  ret = cache.call(request) {|req| adapter.call(req) }
@@ -80,7 +80,7 @@ module Restify
80
80
  end
81
81
 
82
82
  def marshal_load(dump)
83
- initialize dump.delete(:uri), \
83
+ initialize dump.delete(:uri),
84
84
  headers: dump.fetch(:headers)
85
85
  end
86
86
 
data/lib/restify/error.rb CHANGED
@@ -55,8 +55,8 @@ module Restify
55
55
 
56
56
  def initialize(response)
57
57
  @response = response
58
- super "#{response.message} (#{response.code}) for `#{response.uri}':\n" \
59
- " #{errors.inspect}"
58
+ super("#{response.message} (#{response.code}) for `#{response.uri}':\n " \
59
+ "#{errors.inspect}")
60
60
  end
61
61
 
62
62
  # Return response status.
data/lib/restify/link.rb CHANGED
@@ -25,10 +25,10 @@ module Restify
25
25
  end
26
26
 
27
27
  class << self
28
- REGEXP_URI = /<[^>]*>\s*/.freeze
29
- REGEXP_PAR = /;\s*\w+\s*=\s*/i.freeze
30
- REGEXP_QUT = /"[^"]*"\s*/.freeze
31
- REGEXP_ARG = /\w+\s*/i.freeze
28
+ REGEXP_URI = /<[^>]*>\s*/
29
+ REGEXP_PAR = /;\s*\w+\s*=\s*/i
30
+ REGEXP_QUT = /"[^"]*"\s*/
31
+ REGEXP_ARG = /\w+\s*/i
32
32
 
33
33
  def parse(string)
34
34
  scanner = StringScanner.new(string.strip)
@@ -9,11 +9,6 @@ module Restify
9
9
  # Parses generic data structures into resources
10
10
  #
11
11
  module Parsing
12
- def self.included(base)
13
- base.extend ClassMethods
14
- base.indifferent_access = true
15
- end
16
-
17
12
  def load
18
13
  parse deserialized_body, root: true
19
14
  end
@@ -24,12 +19,10 @@ module Restify
24
19
  data = object.each_with_object({}) {|each, obj| parse_data(each, obj) }
25
20
  relations = object.each_with_object({}) {|each, obj| parse_rels(each, obj) }
26
21
 
27
- data = with_indifferent_access(data) if self.class.indifferent_access?
28
-
29
22
  Resource.new context,
30
- data: data,
23
+ data:,
31
24
  response: root ? response : nil,
32
- relations: relations
25
+ relations:
33
26
 
34
27
  when Array
35
28
  object.map {|each| parse(each) }
@@ -58,18 +51,6 @@ module Restify
58
51
 
59
52
  relations[name] = pair[1].to_s
60
53
  end
61
-
62
- def with_indifferent_access(data)
63
- Hashie::Mash.new data
64
- end
65
-
66
- module ClassMethods
67
- def indifferent_access?
68
- @indifferent_access
69
- end
70
-
71
- attr_writer :indifferent_access
72
- end
73
54
  end
74
55
  end
75
56
  end
@@ -16,7 +16,7 @@ module Restify
16
16
  @resource ||= begin
17
17
  resource = load
18
18
 
19
- resource = Resource.new context, response: response, data: resource unless resource.is_a? Restify::Resource
19
+ resource = Resource.new context, response:, data: resource unless resource.is_a? Restify::Resource
20
20
 
21
21
  resource._restify_response = response
22
22
  merge_relations! resource._restify_relations
@@ -25,8 +25,8 @@ module Restify
25
25
  self
26
26
  end
27
27
 
28
- def then(&block)
29
- Promise.new([self], &block)
28
+ def then(&)
29
+ Promise.new([self], &)
30
30
  end
31
31
 
32
32
  def execute(timeout = nil)
@@ -7,7 +7,7 @@ module Restify
7
7
  end
8
8
 
9
9
  def store(name, uri, **opts)
10
- @registry[name] = Context.new uri, **opts
10
+ @registry[name] = Context.new(uri, **opts)
11
11
  end
12
12
 
13
13
  def fetch(name)
@@ -19,32 +19,35 @@ module Restify
19
19
  @template = Addressable::Template.new template
20
20
  end
21
21
 
22
- def request(method, data, params, opts = {})
23
- context.request method, expand(params), **opts, data: data
22
+ def request(method:, params: {}, **opts)
23
+ context.request(method, expand(params), **opts)
24
24
  end
25
25
 
26
- def get(params = {}, opts = {})
27
- request :get, nil, params, opts
26
+ def get(data = {}, params: {}, **opts)
27
+ request(**opts, method: :get, params: data.merge(params))
28
28
  end
29
29
 
30
- def head(params = {}, opts = {})
31
- request :head, nil, params, opts
30
+ def head(data = {}, params: {}, **opts)
31
+ request(**opts, method: :head, params: data.merge(params))
32
32
  end
33
33
 
34
- def delete(params = {}, opts = {})
35
- request :delete, nil, params, opts
34
+ def delete(data = {}, params: {}, **opts)
35
+ request(**opts, method: :delete, params: data.merge(params))
36
36
  end
37
37
 
38
- def post(data = {}, params = {}, opts = {})
39
- request :post, data, params, opts
38
+ def post(data = nil, **opts)
39
+ opts[:data] = data unless opts.key?(:data)
40
+ request(**opts, method: :post)
40
41
  end
41
42
 
42
- def put(data = {}, params = {}, opts = {})
43
- request :put, data, params, opts
43
+ def put(data = nil, **opts)
44
+ opts[:data] = data unless opts.key?(:data)
45
+ request(**opts, method: :put)
44
46
  end
45
47
 
46
- def patch(data = {}, params = {}, opts = {})
47
- request :patch, data, params, opts
48
+ def patch(data = nil, **opts)
49
+ opts[:data] = data unless opts.key?(:data)
50
+ request(**opts, method: :patch)
48
51
  end
49
52
 
50
53
  def ==(other)
@@ -72,14 +75,39 @@ module Restify
72
75
  private
73
76
 
74
77
  def convert(params)
75
- params.each_pair.each_with_object({}) do |param, hash|
78
+ params.each_pair.with_object({}) do |param, hash|
76
79
  hash[param[0]] = convert_param param[1]
77
80
  end
78
81
  end
79
82
 
80
- def convert_param(value)
81
- return value.to_param.to_s if value.respond_to?(:to_param)
83
+ def convert_param(value, nesting: true)
84
+ # Convert parameters into values acceptable in a
85
+ # Addressable::Template, with some support for #to_param, but not
86
+ # for basic types.
87
+ if value == nil || # rubocop:disable Style/NilComparison
88
+ value.is_a?(Numeric) ||
89
+ value.is_a?(Symbol) ||
90
+ value.is_a?(Hash) ||
91
+ value == true ||
92
+ value == false ||
93
+ value.respond_to?(:to_str)
94
+ return value
95
+ end
96
+
97
+ # Handle array-link things first to *not* call #to_params on them,
98
+ # as that will concatenation any Array to "a/b/c". Instead, we
99
+ # want to check one level of basic types only.
100
+ if value.respond_to?(:to_ary)
101
+ return nesting ? value.to_ary.map {|val| convert_param(val, nesting: false) } : value
102
+ end
103
+
104
+ # Handle Rails' #to_param for non-basic types
105
+ if value.respond_to?(:to_param)
106
+ return value.to_param
107
+ end
82
108
 
109
+ # Otherwise, pass raw value to Addressable::Template and let it
110
+ # explode.
83
111
  value
84
112
  end
85
113
 
@@ -29,12 +29,12 @@ module Restify
29
29
  #
30
30
  attr_reader :timeout
31
31
 
32
- def initialize(opts = {})
33
- @method = opts.fetch(:method, :get).downcase
34
- @uri = opts.fetch(:uri) { raise ArgumentError.new ':uri required.' }
35
- @data = opts.fetch(:data, nil)
36
- @timeout = opts.fetch(:timeout, 300)
37
- @headers = opts.fetch(:headers, {})
32
+ def initialize(uri:, method: :get, data: nil, timeout: 300, headers: {})
33
+ @uri = uri
34
+ @method = method.to_s.downcase
35
+ @data = data
36
+ @timeout = timeout
37
+ @headers = headers
38
38
 
39
39
  @headers['Content-Type'] ||= 'application/json' if json?
40
40
  end
@@ -71,9 +71,9 @@ module Restify
71
71
  @target = target
72
72
 
73
73
  if @target
74
- super "Operation on #{@target} timed out"
74
+ super("Operation on #{@target} timed out")
75
75
  else
76
- super 'Operation timed out'
76
+ super('Operation timed out')
77
77
  end
78
78
  end
79
79
  end
@@ -2,9 +2,9 @@
2
2
 
3
3
  module Restify
4
4
  module VERSION
5
- MAJOR = 1
6
- MINOR = 15
7
- PATCH = 2
5
+ MAJOR = 2
6
+ MINOR = 0
7
+ PATCH = 0
8
8
  STAGE = nil
9
9
  STRING = [MAJOR, MINOR, PATCH, STAGE].compact.join('.').freeze
10
10
 
data/lib/restify.rb CHANGED
@@ -4,7 +4,6 @@ require 'forwardable'
4
4
 
5
5
  require 'restify/version'
6
6
 
7
- require 'hashie'
8
7
  require 'concurrent'
9
8
  require 'addressable/uri'
10
9
  require 'addressable/template'
@@ -1,36 +1,40 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
+ require 'active_support'
5
+ require 'active_support/cache'
4
6
 
5
7
  describe Restify::Cache do
6
- subject { cache }
8
+ subject(:cache) { described_class.new(store) }
7
9
 
8
- let(:store) { double 'store' }
9
- let(:cache) { described_class.new store }
10
+ let(:store) { instance_double(ActiveSupport::Cache::Store) }
10
11
 
11
12
  describe '#call' do
12
- let(:request) { double 'request' }
13
- let(:promise0) { double 'promise0' }
14
- let(:promise1) { double 'promise1' }
15
- let(:response) { double 'response' }
13
+ let(:request) { instance_double(Restify::Request) }
14
+ let(:promise0) { instance_double(Restify::Promise, 'promise0') } # rubocop:disable RSpec/IndexedLet
15
+ let(:promise1) { instance_double(Restify::Promise, 'promise1') } # rubocop:disable RSpec/IndexedLet
16
+ let(:response) { instance_double(Restify::Response) }
16
17
 
17
18
  it 'yields with promises' do
18
- expect(promise0).to receive(:then).and_yield(response).and_return(promise1)
19
+ allow(promise0).to receive(:then).and_yield(response).and_return(promise1)
19
20
 
20
- expect(subject.call(request) { promise0 }).to eq promise1
21
+ expect(cache.call(request) { promise0 }).to eq promise1
21
22
  end
22
23
 
23
24
  it 'caches new responses' do
24
- expect(promise0).to receive(:then).and_yield(response)
25
+ allow(promise0).to receive(:then).and_yield(response)
26
+
27
+ # TODO: Do not stub inside tested object
25
28
  expect(cache).to receive(:cache).with(response)
26
29
 
27
- subject.call(request) { promise0 }
30
+ cache.call(request) { promise0 }
28
31
  end
29
32
 
30
33
  it 'returns with match' do
34
+ # TODO: Do not stub inside tested object
31
35
  expect(cache).to receive(:match).with(request).and_return(response)
32
36
 
33
- expect(subject.call(request)).to eq response
37
+ expect(cache.call(request)).to eq response
34
38
  end
35
39
  end
36
40
  end
@@ -20,7 +20,7 @@ describe Restify::Context do
20
20
  describe '#adapter' do
21
21
  subject { super().options[:adapter] }
22
22
 
23
- let(:kwargs) { {adapter: double('adapter')} }
23
+ let(:kwargs) { {adapter: instance_double(Restify::Adapter::Base)} }
24
24
 
25
25
  it 'adapter is not serialized' do
26
26
  expect(subject).to equal nil
@@ -30,7 +30,7 @@ describe Restify::Context do
30
30
  describe '#cache' do
31
31
  subject { super().options[:cache] }
32
32
 
33
- let(:kwargs) { {adapter: double('cache')} }
33
+ let(:kwargs) { {cache: Object.new} }
34
34
 
35
35
  it 'cache is not serialized' do
36
36
  expect(subject).to equal nil
@@ -52,7 +52,12 @@ describe Restify::Context do
52
52
  subject { load }
53
53
 
54
54
  let(:dump) { YAML.dump(context) }
55
- let(:load) { YAML.load(dump) } # rubocop:disable Security/YAMLLoad
55
+
56
+ if RUBY_VERSION >= '3.1'
57
+ let(:load) { YAML.safe_load(dump, permitted_classes: [Restify::Context, Symbol]) }
58
+ else
59
+ let(:load) { YAML.load(dump) }
60
+ end
56
61
 
57
62
  include_examples 'serialization'
58
63
  end