restify 1.15.2 → 2.0.0

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 (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