acfs 1.0.0.dev.1.b305 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +64 -0
  3. data/README.md +2 -2
  4. data/acfs.gemspec +4 -4
  5. data/lib/acfs.rb +7 -5
  6. data/lib/acfs/adapter/base.rb +0 -2
  7. data/lib/acfs/adapter/typhoeus.rb +17 -13
  8. data/lib/acfs/collections/paginatable.rb +10 -10
  9. data/lib/acfs/configuration.rb +4 -5
  10. data/lib/acfs/errors.rb +10 -9
  11. data/lib/acfs/global.rb +16 -7
  12. data/lib/acfs/location.rb +11 -11
  13. data/lib/acfs/middleware/base.rb +1 -2
  14. data/lib/acfs/middleware/json.rb +27 -0
  15. data/lib/acfs/middleware/logger.rb +0 -2
  16. data/lib/acfs/middleware/msgpack.rb +30 -0
  17. data/lib/acfs/middleware/print.rb +0 -2
  18. data/lib/acfs/middleware/serializer.rb +39 -0
  19. data/lib/acfs/operation.rb +5 -5
  20. data/lib/acfs/request.rb +4 -4
  21. data/lib/acfs/request/callbacks.rb +2 -3
  22. data/lib/acfs/resource.rb +2 -2
  23. data/lib/acfs/resource/attributes.rb +10 -37
  24. data/lib/acfs/resource/attributes/base.rb +10 -19
  25. data/lib/acfs/resource/attributes/boolean.rb +10 -8
  26. data/lib/acfs/resource/attributes/date_time.rb +6 -9
  27. data/lib/acfs/resource/attributes/dict.rb +37 -0
  28. data/lib/acfs/resource/attributes/float.rb +11 -5
  29. data/lib/acfs/resource/attributes/integer.rb +7 -5
  30. data/lib/acfs/resource/attributes/list.rb +13 -6
  31. data/lib/acfs/resource/attributes/string.rb +3 -5
  32. data/lib/acfs/resource/attributes/uuid.rb +8 -17
  33. data/lib/acfs/resource/loadable.rb +0 -1
  34. data/lib/acfs/resource/locatable.rb +0 -4
  35. data/lib/acfs/resource/operational.rb +0 -2
  36. data/lib/acfs/resource/persistence.rb +17 -17
  37. data/lib/acfs/resource/query_methods.rb +3 -5
  38. data/lib/acfs/resource/service.rb +0 -2
  39. data/lib/acfs/resource/validation.rb +0 -2
  40. data/lib/acfs/response.rb +1 -2
  41. data/lib/acfs/response/formats.rb +1 -2
  42. data/lib/acfs/response/status.rb +3 -5
  43. data/lib/acfs/runner.rb +21 -11
  44. data/lib/acfs/service.rb +4 -6
  45. data/lib/acfs/service/middleware.rb +20 -30
  46. data/lib/acfs/service/middleware/stack.rb +63 -0
  47. data/lib/acfs/singleton_resource.rb +0 -2
  48. data/lib/acfs/stub.rb +30 -21
  49. data/lib/acfs/util.rb +1 -1
  50. data/lib/acfs/version.rb +4 -2
  51. data/spec/acfs/adapter/typhoeus_spec.rb +12 -4
  52. data/spec/acfs/collection_spec.rb +45 -33
  53. data/spec/acfs/configuration_spec.rb +9 -1
  54. data/spec/acfs/global_spec.rb +21 -3
  55. data/spec/acfs/middleware/json_spec.rb +63 -0
  56. data/spec/acfs/middleware/msgpack_spec.rb +60 -0
  57. data/spec/acfs/operation_spec.rb +10 -0
  58. data/spec/acfs/request/callbacks_spec.rb +8 -8
  59. data/spec/acfs/request_spec.rb +5 -5
  60. data/spec/acfs/resource/attributes/boolean_spec.rb +40 -9
  61. data/spec/acfs/resource/attributes/date_time_spec.rb +29 -35
  62. data/spec/acfs/resource/attributes/dict_spec.rb +75 -0
  63. data/spec/acfs/resource/attributes/float_spec.rb +48 -9
  64. data/spec/acfs/resource/attributes/integer_spec.rb +34 -0
  65. data/spec/acfs/resource/attributes/list_spec.rb +43 -19
  66. data/spec/acfs/resource/attributes/uuid_spec.rb +31 -54
  67. data/spec/acfs/resource/attributes_spec.rb +17 -31
  68. data/spec/acfs/resource/locatable_spec.rb +2 -2
  69. data/spec/acfs/resource/persistance_spec.rb +65 -34
  70. data/spec/acfs/resource/query_methods_spec.rb +87 -87
  71. data/spec/acfs/resource/validation_spec.rb +4 -5
  72. data/spec/acfs/response/formats_spec.rb +1 -1
  73. data/spec/acfs/response/status_spec.rb +1 -1
  74. data/spec/acfs/runner_spec.rb +28 -3
  75. data/spec/acfs/service/middleware_spec.rb +4 -20
  76. data/spec/acfs/service_spec.rb +3 -5
  77. data/spec/acfs/singleton_resource_spec.rb +1 -2
  78. data/spec/acfs/stub_spec.rb +137 -22
  79. data/spec/acfs_spec.rb +22 -19
  80. data/spec/spec_helper.rb +2 -1
  81. data/spec/support/service.rb +10 -6
  82. data/spec/support/shared/find_callbacks.rb +7 -7
  83. metadata +37 -30
  84. data/lib/acfs/middleware/json_decoder.rb +0 -16
  85. data/lib/acfs/middleware/json_encoder.rb +0 -20
  86. data/lib/acfs/middleware/msgpack_decoder.rb +0 -26
  87. data/lib/acfs/middleware/msgpack_encoder.rb +0 -19
  88. data/spec/acfs/middleware/json_decoder_spec.rb +0 -45
  89. data/spec/acfs/middleware/msgpack_decoder_spec.rb +0 -36
@@ -1,7 +1,6 @@
1
1
  require 'acfs/service/middleware'
2
2
 
3
3
  module Acfs
4
-
5
4
  # User {Acfs::Service} to define your services. That includes
6
5
  # an identity used to identify the service in configuration files
7
6
  # and middlewares the service uses.
@@ -35,14 +34,14 @@ module Acfs
35
34
  # @return [Location]
36
35
  #
37
36
  def location(resource_class, opts = {})
38
- opts.reverse_merge! self.options
37
+ opts.reverse_merge! options
39
38
 
40
39
  action = opts[:action] || :list
41
40
 
42
- path = if Hash === opts[:path] && opts[:path].has_key?(action)
41
+ path = if opts[:path].is_a?(Hash) && opts[:path].key?(action)
43
42
  opts[:path].fetch(action)
44
43
  else
45
- path = if Hash === opts[:path]
44
+ path = if opts[:path].is_a?(Hash)
46
45
  opts[:path][:all].to_s
47
46
  else
48
47
  opts[:path].to_s
@@ -59,7 +58,6 @@ module Acfs
59
58
  end
60
59
 
61
60
  class << self
62
-
63
61
  # @api public
64
62
  #
65
63
  # @overload identity()
@@ -83,7 +81,7 @@ module Acfs
83
81
  #
84
82
  def base_url
85
83
  unless (base = Acfs::Configuration.current.locate identity)
86
- raise ArgumentError, "#{identity} not configured. Add `locate '#{identity.to_s.underscore}', 'http://service.url/'` to your configuration."
84
+ raise ArgumentError.new "#{identity} not configured. Add `locate '#{identity.to_s.underscore}', 'http://service.url/'` to your configuration."
87
85
  end
88
86
 
89
87
  base.to_s
@@ -1,6 +1,7 @@
1
+ require 'acfs/service/middleware/stack'
2
+
1
3
  module Acfs
2
4
  class Service
3
-
4
5
  # Module providing all function to register middlewares
5
6
  # on services and process queued request through the
6
7
  # middleware stack.
@@ -16,28 +17,25 @@ module Acfs
16
17
  end
17
18
 
18
19
  module ClassMethods
19
-
20
- # @api public
20
+ # @!method use(klass, *args, &block)
21
+ # @api public
21
22
  #
22
- # Register a new middleware to be used for this service.
23
+ # Register a new middleware to be used for this service.
23
24
  #
24
- # @example
25
- # class MyService < Acfs::Service
26
- # self.base_url = 'http://my.srv'
27
- # use Acfs::Middleware::JsonDecoder
28
- # end
25
+ # @example
26
+ # class MyService < Acfs::Service
27
+ # self.base_url = 'http://my.srv'
28
+ # use Acfs::Middleware::JSON
29
+ # end
29
30
  #
30
- # @param [Class] klass Middleware class to instantiate and append to middleware stack.
31
- # @param [Hash, Object] options Options to delegate to middleware class initializer.
32
- # @return [undefined]
31
+ # @param [Class] klass Middleware class to append
32
+ # @param [Array<Object>] args Arguments passed to klass initialize
33
+ # @param [Proc] block Block passed to klass initialize
34
+ # @return [undefined]
33
35
  #
34
- def use(klass, options = {})
35
- @middlewares ||= []
36
-
37
- return false if @middlewares.include? klass
38
-
39
- @middlewares << klass
40
- @middleware = klass.new(middleware, options)
36
+ def use(klass, *args, &block)
37
+ # Backward compatible behavior
38
+ middleware.insert(0, klass, *args, &block)
41
39
  end
42
40
 
43
41
  # @api private
@@ -47,19 +45,11 @@ module Acfs
47
45
  # @return [#call]
48
46
  #
49
47
  def middleware
50
- @middleware ||= proc { |request| request}
48
+ @middleware ||= Stack.new
51
49
  end
52
50
 
53
- # @api public
54
- #
55
- # Clear all registered middlewares.
56
- #
57
- # @return [undefined]
58
- #
59
- def clear
60
- @middleware = nil
61
- @middlewares = []
62
- end
51
+ # @deprecated
52
+ delegate :clear, to: :middleware
63
53
  end
64
54
  end
65
55
  end
@@ -0,0 +1,63 @@
1
+ module Acfs
2
+ class Service
3
+ module Middleware
4
+ class Stack
5
+ include Enumerable
6
+
7
+ MUTEX = Mutex.new
8
+ IDENTITY = -> (i) { i }
9
+
10
+ attr_reader :middlewares
11
+
12
+ def initialize
13
+ @middlewares = []
14
+ end
15
+
16
+ def call(request)
17
+ build! unless @app
18
+
19
+ @app.call request
20
+ end
21
+
22
+ def build!
23
+ return if @app
24
+
25
+ MUTEX.synchronize do
26
+ return if @app
27
+
28
+ @app = build
29
+ end
30
+ end
31
+
32
+ def build(app = IDENTITY)
33
+ middlewares.reverse.inject(app) do |next_middleware, current_middleware|
34
+ klass, args, block = current_middleware
35
+ args ||= []
36
+
37
+ if klass.is_a?(Class)
38
+ klass.new(next_middleware, *args, &block)
39
+ elsif klass.respond_to?(:call)
40
+ lambda do |env|
41
+ next_middleware.call(klass.call(env, *args))
42
+ end
43
+ else
44
+ fail "Invalid middleware, doesn't respond to `call`: #{klass.inspect}"
45
+ end
46
+ end
47
+ end
48
+
49
+ def insert(index, klass, *args, &block)
50
+ middlewares.insert(index, [klass, args, block])
51
+ end
52
+
53
+ def each
54
+ middlewares.each { |x| yield x.first }
55
+ end
56
+
57
+ def clear
58
+ middlewares.clear
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -1,5 +1,4 @@
1
1
  module Acfs
2
-
3
2
  # Acfs SingletonResources
4
3
  #
5
4
  # Usage explanation:
@@ -14,7 +13,6 @@ module Acfs
14
13
  # always only a single instance of the resource is being returned
15
14
  #
16
15
  class SingletonResource < Acfs::Resource
17
-
18
16
  # @api public
19
17
  #
20
18
  # Destroy resource by sending a DELETE request.
@@ -1,7 +1,6 @@
1
1
  require 'rack/utils'
2
2
 
3
3
  module Acfs
4
-
5
4
  # Global handler for stubbing resources.
6
5
  #
7
6
  class Stub
@@ -14,7 +13,7 @@ module Acfs
14
13
 
15
14
  @opts[:with].stringify_keys! if @opts[:with].is_a? Hash
16
15
  @opts[:return].stringify_keys! if @opts[:return].is_a? Hash
17
- @opts[:return].map! { |h| h.stringify_keys! if h.is_a? Hash } if @opts[:return].is_a? Array
16
+ @opts[:return].map! {|h| h.stringify_keys! if h.is_a? Hash } if @opts[:return].is_a? Array
18
17
  end
19
18
 
20
19
  def accept?(op)
@@ -22,14 +21,21 @@ module Acfs
22
21
 
23
22
  params = op.full_params.stringify_keys
24
23
  data = op.data.stringify_keys
25
-
26
- return true if opts[:with] == params || data == opts[:with]
27
- return true if (opts[:with].nil? && params.empty? && data.empty?)
28
-
29
- return true if opts[:with].reject { |k, v| v.nil? } == params.reject { |k, v| v.nil? }
30
- return true if opts[:with].reject { |k, v| v.nil? } == data.reject { |k, v| v.nil? }
31
-
32
- false
24
+ with = opts[:with]
25
+
26
+ return true if with.nil?
27
+
28
+ case opts.fetch(:match, :inclusion)
29
+ when :legacy
30
+ return true if with.empty? && params.empty? && data.empty?
31
+ return true if with.reject {|_, v| v.nil? } == params.reject {|_, v| v.nil? }
32
+ return true if with.reject {|_, v| v.nil? } == data.reject {|_, v| v.nil? }
33
+ false
34
+ when :inclusion
35
+ with.each_pair.all? do |k, v|
36
+ (params.key?(k) && params[k] == v) || (data.key?(k) && data[k] == v)
37
+ end
38
+ end
33
39
  end
34
40
 
35
41
  def calls
@@ -50,17 +56,20 @@ module Acfs
50
56
  if err
51
57
  raise_error op, err, opts[:return]
52
58
  elsif data
59
+ data = data.call(op) if data.respond_to?(:call)
60
+
53
61
  response = Acfs::Response.new op.request,
54
- headers: opts[:headers] || {},
55
- status: opts[:status] || 200,
56
- data: data || {}
62
+ headers: opts[:headers] || {},
63
+ status: opts[:status] || 200,
64
+ data: data || {}
57
65
  op.call data, response
58
66
  else
59
- raise ArgumentError, 'Unsupported stub.'
67
+ raise ArgumentError.new 'Unsupported stub.'
60
68
  end
61
69
  end
62
70
 
63
71
  private
72
+
64
73
  def raise_error(op, name, data)
65
74
  raise name if name.is_a? Class
66
75
  data.stringify_keys! if data.respond_to? :stringify_keys!
@@ -69,13 +78,12 @@ module Acfs
69
78
  end
70
79
 
71
80
  class << self
72
-
73
81
  # Stub a resource with given handler block. An already created handler
74
82
  # for same resource class will be overridden.
75
83
  #
76
- def resource(klass, action, opts = {}, &block)
84
+ def resource(klass, action, opts = {}, &_block)
77
85
  action = action.to_sym
78
- raise ArgumentError, "Unknown action `#{action}`." unless ACTIONS.include? action
86
+ raise ArgumentError.new "Unknown action `#{action}`." unless ACTIONS.include? action
79
87
 
80
88
  Stub.new(opts).tap do |stub|
81
89
  stubs[klass] ||= {}
@@ -96,11 +104,11 @@ module Acfs
96
104
  @enabled ||= false
97
105
  end
98
106
 
99
- def enable;
107
+ def enable
100
108
  @enabled = true
101
109
  end
102
110
 
103
- def disable;
111
+ def disable
104
112
  @enabled = false
105
113
  end
106
114
 
@@ -118,7 +126,7 @@ module Acfs
118
126
  return false unless (classes = stubs[op.resource])
119
127
  return false unless (stubs = classes[op.action])
120
128
 
121
- accepted_stubs = stubs.select { |stub| stub.accept? op }
129
+ accepted_stubs = stubs.select {|stub| stub.accept? op }
122
130
 
123
131
  raise AmbiguousStubError.new stubs: accepted_stubs, operation: op if accepted_stubs.size > 1
124
132
 
@@ -142,8 +150,9 @@ module Acfs
142
150
  end
143
151
 
144
152
  private
153
+
145
154
  def pretty_print
146
- out = String.new
155
+ out = ''
147
156
  stubs.each do |klass, actions|
148
157
  out << ' ' << klass.name << ":\n"
149
158
  actions.each do |action, stubs|
@@ -7,7 +7,7 @@ module Acfs
7
7
  end
8
8
 
9
9
  def __invoke__
10
- __callbacks__.each{|c| c.call self}
10
+ __callbacks__.each {|c| c.call self }
11
11
  end
12
12
  end
13
13
 
@@ -3,10 +3,12 @@ module Acfs
3
3
  MAJOR = 1
4
4
  MINOR = 0
5
5
  PATCH = 0
6
- STAGE = 'dev'
6
+ STAGE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, PATCH, STAGE].reject(&:nil?).join('.')
9
9
 
10
- def self.to_s; STRING end
10
+ def self.to_s
11
+ STRING
12
+ end
11
13
  end
12
14
  end
@@ -5,16 +5,24 @@ describe Acfs::Adapter::Typhoeus do
5
5
  before { WebMock.allow_net_connect! }
6
6
 
7
7
  it 'raises an error' do
8
- request1 = Acfs::Request.new 'http://altimos.de/404.1' do |rsp|
8
+ request1 = Acfs::Request.new 'http://altimos.de/404.1' do |_rsp|
9
9
  raise '404-1'
10
10
  end
11
- request2 = Acfs::Request.new 'http://altimos.de/404.2' do |rsp|
11
+ request2 = Acfs::Request.new 'http://altimos.de/404.2' do |_rsp|
12
12
  raise '404-2'
13
13
  end
14
14
  adapter.queue request1
15
15
  adapter.queue request2
16
16
 
17
- expect{ adapter.start }.to raise_error /404\-[12]/
18
- expect{ adapter.start }.to_not raise_error
17
+ expect { adapter.start }.to raise_error(/404\-[12]/)
18
+ expect { adapter.start }.to_not raise_error
19
+ end
20
+
21
+ it 'passes arguments to typhoeus hydra' do
22
+ value = {key: 1, key2: 2}
23
+
24
+ expect(::Typhoeus::Hydra).to receive(:new).with(value)
25
+
26
+ described_class.new(**value).send :hydra
19
27
  end
20
28
  end
@@ -11,47 +11,59 @@ describe Acfs::Collection do
11
11
 
12
12
  context 'without explicit page parameter' do
13
13
  before do
14
- stub_request(:get, 'http://users.example.org/users').
15
- to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
16
- headers: {'X-Total-Pages' => '2'})
14
+ stub_request(:get, 'http://users.example.org/users')
15
+ .to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
16
+ headers: {
17
+ 'X-Total-Pages' => '2',
18
+ 'X-Total-Count' => '10'
19
+ })
17
20
  end
18
21
 
19
22
  its(:total_pages) { should eq 2 }
20
23
  its(:current_page) { should eq 1 }
24
+ its(:total_count) { should eq 10 }
21
25
  end
22
26
 
23
27
  context 'with page parameter' do
24
28
  let(:params) { {page: 2} }
25
29
  before do
26
- stub_request(:get, 'http://users.example.org/users?page=2').
27
- to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
28
- headers: {'X-Total-Pages' => '2'})
30
+ stub_request(:get, 'http://users.example.org/users?page=2')
31
+ .to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
32
+ headers: {
33
+ 'X-Total-Pages' => '2',
34
+ 'X-Total-Count' => '10'
35
+ })
29
36
  end
30
37
 
31
38
  its(:total_pages) { should eq 2 }
32
39
  its(:current_page) { should eq 2 }
40
+ its(:total_count) { should eq 10 }
33
41
  end
34
42
 
35
43
  context 'with non-numerical page parameter' do
36
44
  let(:params) { {page: 'e546f5'} }
37
45
  before do
38
- stub_request(:get, 'http://users.example.org/users?page=e546f5').
39
- to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
40
- headers: {'X-Total-Pages' => '2'})
46
+ stub_request(:get, 'http://users.example.org/users?page=e546f5')
47
+ .to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
48
+ headers: {
49
+ 'X-Total-Pages' => '2',
50
+ 'X-Total-Count' => '10'
51
+ })
41
52
  end
42
53
 
43
54
  its(:total_pages) { should eq 2 }
44
55
  its(:current_page) { should eq 'e546f5' }
56
+ its(:total_count) { should eq 10 }
45
57
  end
46
58
 
47
59
  describe '#next_page' do
48
60
  before do
49
- stub_request(:get, 'http://users.example.org/users').
50
- to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
51
- headers: {
52
- 'X-Total-Pages' => '2',
53
- 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
54
- })
61
+ stub_request(:get, 'http://users.example.org/users')
62
+ .to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
63
+ headers: {
64
+ 'X-Total-Pages' => '2',
65
+ 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
66
+ })
55
67
  end
56
68
  let!(:req) do
57
69
  stub_request(:get, 'http://users.example.org/users?page=2').to_return response([])
@@ -70,12 +82,12 @@ describe Acfs::Collection do
70
82
 
71
83
  describe '#prev_page' do
72
84
  before do
73
- stub_request(:get, 'http://users.example.org/users?page=2').
74
- to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
75
- headers: {
76
- 'X-Total-Pages' => '2',
77
- 'Link' => '<http://users.example.org/users>; rel="prev"'
78
- })
85
+ stub_request(:get, 'http://users.example.org/users?page=2')
86
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
87
+ headers: {
88
+ 'X-Total-Pages' => '2',
89
+ 'Link' => '<http://users.example.org/users>; rel="prev"'
90
+ })
79
91
  end
80
92
  let!(:req) do
81
93
  stub_request(:get, 'http://users.example.org/users').to_return response([])
@@ -94,12 +106,12 @@ describe Acfs::Collection do
94
106
 
95
107
  describe '#first_page' do
96
108
  before do
97
- stub_request(:get, 'http://users.example.org/users?page=2').
98
- to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
99
- headers: {
100
- 'X-Total-Pages' => '2',
101
- 'Link' => '<http://users.example.org/users>; rel="first"'
102
- })
109
+ stub_request(:get, 'http://users.example.org/users?page=2')
110
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
111
+ headers: {
112
+ 'X-Total-Pages' => '2',
113
+ 'Link' => '<http://users.example.org/users>; rel="first"'
114
+ })
103
115
  end
104
116
  let!(:req) do
105
117
  stub_request(:get, 'http://users.example.org/users').to_return response([])
@@ -118,12 +130,12 @@ describe Acfs::Collection do
118
130
 
119
131
  describe '#last_page' do
120
132
  before do
121
- stub_request(:get, 'http://users.example.org/users?page=2').
122
- to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
123
- headers: {
124
- 'X-Total-Pages' => '2',
125
- 'Link' => '<http://users.example.org/users?page=12>; rel="last"'
126
- })
133
+ stub_request(:get, 'http://users.example.org/users?page=2')
134
+ .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
135
+ headers: {
136
+ 'X-Total-Pages' => '2',
137
+ 'Link' => '<http://users.example.org/users?page=12>; rel="last"'
138
+ })
127
139
  end
128
140
  let!(:req) do
129
141
  stub_request(:get, 'http://users.example.org/users?page=12').to_return response([])