acfs 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +50 -0
  3. data/README.md +3 -4
  4. data/acfs.gemspec +19 -10
  5. data/lib/acfs.rb +2 -0
  6. data/lib/acfs/adapter/base.rb +6 -8
  7. data/lib/acfs/adapter/typhoeus.rb +26 -7
  8. data/lib/acfs/collection.rb +2 -1
  9. data/lib/acfs/collections/paginatable.rb +2 -1
  10. data/lib/acfs/configuration.rb +14 -7
  11. data/lib/acfs/errors.rb +33 -12
  12. data/lib/acfs/global.rb +12 -2
  13. data/lib/acfs/location.rb +9 -5
  14. data/lib/acfs/middleware/base.rb +5 -1
  15. data/lib/acfs/middleware/json.rb +2 -0
  16. data/lib/acfs/middleware/logger.rb +2 -0
  17. data/lib/acfs/middleware/msgpack.rb +2 -0
  18. data/lib/acfs/middleware/print.rb +2 -0
  19. data/lib/acfs/middleware/serializer.rb +2 -0
  20. data/lib/acfs/operation.rb +5 -3
  21. data/lib/acfs/request.rb +3 -0
  22. data/lib/acfs/request/callbacks.rb +5 -1
  23. data/lib/acfs/resource.rb +2 -0
  24. data/lib/acfs/resource/attributes.rb +3 -2
  25. data/lib/acfs/resource/attributes/base.rb +2 -1
  26. data/lib/acfs/resource/attributes/boolean.rb +2 -0
  27. data/lib/acfs/resource/attributes/date_time.rb +2 -1
  28. data/lib/acfs/resource/attributes/dict.rb +2 -0
  29. data/lib/acfs/resource/attributes/float.rb +5 -3
  30. data/lib/acfs/resource/attributes/integer.rb +2 -0
  31. data/lib/acfs/resource/attributes/list.rb +2 -0
  32. data/lib/acfs/resource/attributes/string.rb +2 -0
  33. data/lib/acfs/resource/attributes/uuid.rb +4 -3
  34. data/lib/acfs/resource/dirty.rb +2 -0
  35. data/lib/acfs/resource/initialization.rb +2 -0
  36. data/lib/acfs/resource/loadable.rb +2 -0
  37. data/lib/acfs/resource/locatable.rb +10 -6
  38. data/lib/acfs/resource/operational.rb +2 -1
  39. data/lib/acfs/resource/persistence.rb +7 -4
  40. data/lib/acfs/resource/query_methods.rb +5 -3
  41. data/lib/acfs/resource/service.rb +3 -1
  42. data/lib/acfs/resource/validation.rb +3 -1
  43. data/lib/acfs/response.rb +2 -0
  44. data/lib/acfs/response/formats.rb +2 -0
  45. data/lib/acfs/response/status.rb +3 -1
  46. data/lib/acfs/rspec.rb +2 -0
  47. data/lib/acfs/runner.rb +6 -1
  48. data/lib/acfs/service.rb +8 -2
  49. data/lib/acfs/service/middleware.rb +2 -0
  50. data/lib/acfs/service/middleware/stack.rb +5 -3
  51. data/lib/acfs/singleton_resource.rb +4 -2
  52. data/lib/acfs/stub.rb +33 -11
  53. data/lib/acfs/util.rb +2 -0
  54. data/lib/acfs/version.rb +3 -1
  55. data/lib/acfs/yard.rb +1 -0
  56. data/spec/acfs/adapter/typhoeus_spec.rb +30 -3
  57. data/spec/acfs/collection_spec.rb +7 -5
  58. data/spec/acfs/configuration_spec.rb +2 -0
  59. data/spec/acfs/global_spec.rb +48 -1
  60. data/spec/acfs/location_spec.rb +25 -0
  61. data/spec/acfs/middleware/json_spec.rb +2 -0
  62. data/spec/acfs/middleware/msgpack_spec.rb +2 -0
  63. data/spec/acfs/operation_spec.rb +2 -0
  64. data/spec/acfs/request/callbacks_spec.rb +2 -0
  65. data/spec/acfs/request_spec.rb +3 -1
  66. data/spec/acfs/resource/attributes/boolean_spec.rb +2 -0
  67. data/spec/acfs/resource/attributes/date_time_spec.rb +2 -0
  68. data/spec/acfs/resource/attributes/dict_spec.rb +4 -2
  69. data/spec/acfs/resource/attributes/float_spec.rb +2 -0
  70. data/spec/acfs/resource/attributes/integer_spec.rb +2 -0
  71. data/spec/acfs/resource/attributes/list_spec.rb +5 -3
  72. data/spec/acfs/resource/attributes/uuid_spec.rb +2 -0
  73. data/spec/acfs/resource/attributes_spec.rb +6 -4
  74. data/spec/acfs/resource/dirty_spec.rb +2 -0
  75. data/spec/acfs/resource/initialization_spec.rb +8 -2
  76. data/spec/acfs/resource/loadable_spec.rb +2 -0
  77. data/spec/acfs/resource/locatable_spec.rb +2 -0
  78. data/spec/acfs/resource/persistance_spec.rb +10 -4
  79. data/spec/acfs/resource/query_methods_spec.rb +25 -18
  80. data/spec/acfs/resource/validation_spec.rb +2 -0
  81. data/spec/acfs/response/formats_spec.rb +3 -1
  82. data/spec/acfs/response/status_spec.rb +2 -0
  83. data/spec/acfs/runner_spec.rb +6 -8
  84. data/spec/acfs/service/middleware_spec.rb +2 -0
  85. data/spec/acfs/service_spec.rb +3 -1
  86. data/spec/acfs/singleton_resource_spec.rb +2 -0
  87. data/spec/acfs/stub_spec.rb +2 -0
  88. data/spec/acfs_spec.rb +2 -0
  89. data/spec/spec_helper.rb +11 -6
  90. data/spec/support/hash.rb +2 -0
  91. data/spec/support/response.rb +2 -0
  92. data/spec/support/service.rb +1 -0
  93. data/spec/support/shared/find_callbacks.rb +2 -0
  94. metadata +12 -11
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs
2
4
  class Response
3
5
  # Method to fetch information about response status.
@@ -11,7 +13,7 @@ module Acfs
11
13
  # return response.response_code unless response.nil?
12
14
  # 0
13
15
  end
14
- alias_method :code, :status_code
16
+ alias code status_code
15
17
 
16
18
  # Return true if response was successful indicated by
17
19
  # response status code.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'acfs'
2
4
 
3
5
  RSpec.configure do |config|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'acfs/service/middleware'
2
4
 
3
5
  module Acfs
@@ -59,7 +61,7 @@ module Acfs
59
61
 
60
62
  enqueue_operations
61
63
  start_all
62
- rescue
64
+ rescue StandardError
63
65
  queue.clear
64
66
  raise
65
67
  end
@@ -87,10 +89,13 @@ module Acfs
87
89
 
88
90
  def op_request(op)
89
91
  return if Acfs::Stub.enabled? && Acfs::Stub.stubbed(op)
92
+
90
93
  req = op.service.prepare op.request
91
94
  return unless req.is_a? Acfs::Request
95
+
92
96
  req = prepare req
93
97
  return unless req.is_a? Acfs::Request
98
+
94
99
  yield req
95
100
  end
96
101
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'acfs/service/middleware'
2
4
 
3
5
  module Acfs
@@ -47,12 +49,16 @@ module Acfs
47
49
  opts[:path].to_s
48
50
  end
49
51
 
50
- path = (resource_class.name || 'class').pluralize.underscore if path.blank?
52
+ if path.blank?
53
+ path = (resource_class.name || 'class').pluralize.underscore
54
+ end
51
55
 
52
56
  resource_class.location_default_path(action, path.strip)
53
57
  end
54
58
 
55
- raise ArgumentError.new "Location for `#{action}' explicit disabled by set to nil." if path.nil?
59
+ if path.nil?
60
+ raise ArgumentError.new "Location for `#{action}' explicit disabled by set to nil."
61
+ end
56
62
 
57
63
  Location.new [self.class.base_url.to_s, path.to_s].join('/')
58
64
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'acfs/service/middleware/stack'
2
4
 
3
5
  module Acfs
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs
2
4
  class Service
3
5
  module Middleware
@@ -5,7 +7,7 @@ module Acfs
5
7
  include Enumerable
6
8
 
7
9
  MUTEX = Mutex.new
8
- IDENTITY = -> (i) { i }
10
+ IDENTITY = ->(i) { i }
9
11
 
10
12
  attr_reader :middlewares
11
13
 
@@ -41,7 +43,7 @@ module Acfs
41
43
  next_middleware.call(klass.call(env, *args))
42
44
  end
43
45
  else
44
- fail "Invalid middleware, doesn't respond to `call`: #{klass.inspect}"
46
+ raise "Invalid middleware, doesn't respond to `call`: #{klass.inspect}"
45
47
  end
46
48
  end
47
49
  end
@@ -51,7 +53,7 @@ module Acfs
51
53
  end
52
54
 
53
55
  def each
54
- middlewares.each { |x| yield x.first }
56
+ middlewares.each {|x| yield x.first }
55
57
  end
56
58
 
57
59
  def clear
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs
2
4
  # Acfs SingletonResources
3
5
  #
@@ -71,8 +73,8 @@ module Acfs
71
73
  def all
72
74
  raise ::Acfs::UnsupportedOperation.new
73
75
  end
74
- alias_method :find_by, :all
75
- alias_method :find_by!, :all
76
+ alias find_by all
77
+ alias find_by! all
76
78
 
77
79
  # @api private
78
80
  def location_default_path(_, path)
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'rack/utils'
2
4
 
3
5
  module Acfs
4
6
  # Global handler for stubbing resources.
5
7
  #
6
8
  class Stub
7
- ACTIONS = [:read, :create, :update, :delete, :list]
9
+ ACTIONS = %i[read create update delete list].freeze
8
10
 
9
11
  attr_reader :opts
10
12
 
@@ -13,7 +15,10 @@ module Acfs
13
15
 
14
16
  @opts[:with].stringify_keys! if @opts[:with].is_a? Hash
15
17
  @opts[:return].stringify_keys! if @opts[:return].is_a? Hash
16
- @opts[:return].map! {|h| h.stringify_keys! if h.is_a? Hash } if @opts[:return].is_a? Array
18
+
19
+ if @opts[:return].is_a? Array
20
+ @opts[:return].map! {|h| h.stringify_keys! if h.is_a? Hash }
21
+ end
17
22
  end
18
23
 
19
24
  def accept?(op)
@@ -28,8 +33,13 @@ module Acfs
28
33
  case opts.fetch(:match, :inclusion)
29
34
  when :legacy
30
35
  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? }
36
+ if with.reject {|_, v| v.nil? } == params.reject {|_, v| v.nil? }
37
+ return true
38
+ end
39
+ if with.reject {|_, v| v.nil? } == data.reject {|_, v| v.nil? }
40
+ return true
41
+ end
42
+
33
43
  false
34
44
  when :inclusion
35
45
  with.each_pair.all? do |k, v|
@@ -43,7 +53,9 @@ module Acfs
43
53
  end
44
54
 
45
55
  def called?(count = nil)
46
- count = count.count if count.respond_to? :count # For `5.times` Enumerators
56
+ if count.respond_to? :count
57
+ count = count.count
58
+ end # For `5.times` Enumerators
47
59
  count.nil? ? calls.any? : calls.size == count
48
60
  end
49
61
 
@@ -60,8 +72,8 @@ module Acfs
60
72
 
61
73
  response = Acfs::Response.new op.request,
62
74
  headers: opts[:headers] || {},
63
- status: opts[:status] || 200,
64
- data: data || {}
75
+ status: opts[:status] || 200,
76
+ data: data || {}
65
77
  op.call data, response
66
78
  else
67
79
  raise ArgumentError.new 'Unsupported stub.'
@@ -72,6 +84,7 @@ module Acfs
72
84
 
73
85
  def raise_error(op, name, data)
74
86
  raise name if name.is_a? Class
87
+
75
88
  data.stringify_keys! if data.respond_to? :stringify_keys!
76
89
 
77
90
  op.handle_failure ::Acfs::Response.new op.request, status: Rack::Utils.status_code(name), data: data
@@ -83,7 +96,9 @@ module Acfs
83
96
  #
84
97
  def resource(klass, action, opts = {}, &_block)
85
98
  action = action.to_sym
86
- raise ArgumentError.new "Unknown action `#{action}`." unless ACTIONS.include? action
99
+ unless ACTIONS.include? action
100
+ raise ArgumentError.new "Unknown action `#{action}`."
101
+ end
87
102
 
88
103
  Stub.new(opts).tap do |stub|
89
104
  stubs[klass] ||= {}
@@ -128,7 +143,9 @@ module Acfs
128
143
 
129
144
  accepted_stubs = stubs.select {|stub| stub.accept? op }
130
145
 
131
- raise AmbiguousStubError.new stubs: accepted_stubs, operation: op if accepted_stubs.size > 1
146
+ if accepted_stubs.size > 1
147
+ raise AmbiguousStubError.new stubs: accepted_stubs, operation: op
148
+ end
132
149
 
133
150
  accepted_stubs.first
134
151
  end
@@ -137,6 +154,7 @@ module Acfs
137
154
  stub = stub_for op
138
155
  unless stub
139
156
  return false if allow_requests?
157
+
140
158
  raise RealRequestsNotAllowedError.new <<-MSG.strip.gsub(/^[ ]{12}/, '')
141
159
  No stub found for `#{op.action}' on `#{op.resource.name}' with params `#{op.full_params.inspect}', data `#{op.data.inspect}' and id `#{op.id}'.
142
160
 
@@ -159,8 +177,12 @@ module Acfs
159
177
  stubs.each do |stub|
160
178
  out << " #{action}"
161
179
  out << " with #{stub.opts[:with].inspect}" if stub.opts[:with]
162
- out << " and return #{stub.opts[:return].inspect}" if stub.opts[:return]
163
- out << " and raise #{stub.opts[:raise].inspect}" if stub.opts[:raise]
180
+ if stub.opts[:return]
181
+ out << " and return #{stub.opts[:return].inspect}"
182
+ end
183
+ if stub.opts[:raise]
184
+ out << " and raise #{stub.opts[:raise].inspect}"
185
+ end
164
186
  out << "\n"
165
187
  end
166
188
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs
2
4
  module Util
3
5
  # TODO: Merge wit features in v1.0
@@ -1,7 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Acfs
2
4
  module VERSION
3
5
  MAJOR = 1
4
- MINOR = 3
6
+ MINOR = 4
5
7
  PATCH = 0
6
8
  STAGE = nil
7
9
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # YARD macros
2
3
 
3
4
  # @!macro [new] experimental
@@ -1,14 +1,19 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Acfs::Adapter::Typhoeus do
4
6
  let(:adapter) { described_class.new }
5
- before { WebMock.allow_net_connect! }
7
+
8
+ before do
9
+ stub_request(:any, 'http://example.org').to_return status: 200
10
+ end
6
11
 
7
12
  it 'raises an error' do
8
- request1 = Acfs::Request.new 'http://altimos.de/404.1' do |_rsp|
13
+ request1 = Acfs::Request.new 'http://example.org' do |_rsp|
9
14
  raise '404-1'
10
15
  end
11
- request2 = Acfs::Request.new 'http://altimos.de/404.2' do |_rsp|
16
+ request2 = Acfs::Request.new 'http://example.org' do |_rsp|
12
17
  raise '404-2'
13
18
  end
14
19
  adapter.queue request1
@@ -18,6 +23,28 @@ describe Acfs::Adapter::Typhoeus do
18
23
  expect { adapter.start }.to_not raise_error
19
24
  end
20
25
 
26
+ it 'raises timeout' do
27
+ stub_request(:any, 'http://example.org').to_timeout
28
+
29
+ request = Acfs::Request.new 'http://example.org'
30
+ adapter.queue request
31
+
32
+ expect { adapter.run(request) }.to raise_error(::Acfs::TimeoutError) do |err|
33
+ expect(err.message).to eq 'Timeout reached: GET http://example.org'
34
+ end
35
+ end
36
+
37
+ it 'raises connection errors' do
38
+ WebMock.allow_net_connect!
39
+
40
+ request = Acfs::Request.new 'http://should-never-exists.example.org'
41
+ adapter.queue request
42
+
43
+ expect { adapter.run(request) }.to raise_error(::Acfs::RequestError) do |err|
44
+ expect(err.message).to eq 'Couldn\'t resolve host name: GET http://should-never-exists.example.org'
45
+ end
46
+ end
47
+
21
48
  it 'passes arguments to typhoeus hydra' do
22
49
  value = {key: 1, key2: 2}
23
50
 
@@ -1,10 +1,12 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Acfs::Collection do
4
6
  let(:model) { MyUser }
5
7
 
6
8
  describe 'Pagination' do
7
- let(:params) { Hash.new }
9
+ let(:params) { {} }
8
10
  let!(:collection) { model.all params }
9
11
 
10
12
  subject { Acfs.run; collection }
@@ -62,7 +64,7 @@ describe Acfs::Collection do
62
64
  .to_return response([{id: 1, name: 'Anon', age: 12, born_at: 'Berlin'}],
63
65
  headers: {
64
66
  'X-Total-Pages' => '2',
65
- 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
67
+ 'Link' => '<http://users.example.org/users?page=2>; rel="next"'
66
68
  })
67
69
  end
68
70
  let!(:req) do
@@ -86,7 +88,7 @@ describe Acfs::Collection do
86
88
  .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
87
89
  headers: {
88
90
  'X-Total-Pages' => '2',
89
- 'Link' => '<http://users.example.org/users>; rel="prev"'
91
+ 'Link' => '<http://users.example.org/users>; rel="prev"'
90
92
  })
91
93
  end
92
94
  let!(:req) do
@@ -110,7 +112,7 @@ describe Acfs::Collection do
110
112
  .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
111
113
  headers: {
112
114
  'X-Total-Pages' => '2',
113
- 'Link' => '<http://users.example.org/users>; rel="first"'
115
+ 'Link' => '<http://users.example.org/users>; rel="first"'
114
116
  })
115
117
  end
116
118
  let!(:req) do
@@ -134,7 +136,7 @@ describe Acfs::Collection do
134
136
  .to_return response([{id: 2, name: 'Anno', age: 1604, born_at: 'Santa Maria'}],
135
137
  headers: {
136
138
  'X-Total-Pages' => '2',
137
- 'Link' => '<http://users.example.org/users?page=12>; rel="last"'
139
+ 'Link' => '<http://users.example.org/users?page=12>; rel="last"'
138
140
  })
139
141
  end
140
142
  let!(:req) do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  describe Acfs::Configuration do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'spec_helper'
2
4
 
3
5
  class NotificationCollector
@@ -48,7 +50,8 @@ describe ::Acfs::Global do
48
50
  stub_request(:get, %r{http://users.example.org/users/\d+}).to_return(
49
51
  status: 200,
50
52
  body: '{}',
51
- headers: {'Content-Type' => 'application/json'})
53
+ headers: {'Content-Type' => 'application/json'}
54
+ )
52
55
  end
53
56
 
54
57
  it 'should invoke when both resources' do
@@ -71,6 +74,50 @@ describe ::Acfs::Global do
71
74
  end
72
75
  Acfs.run
73
76
  end
77
+
78
+ context 'with an empty result for a find_by call' do
79
+ before do
80
+ stub_request(:get, %r{http://users.example.org/users})
81
+ .with(query: {id: '2'})
82
+ .to_return(
83
+ status: 200,
84
+ body: '{}',
85
+ headers: {'Content-Type' => 'application/json'}
86
+ )
87
+ end
88
+
89
+ it 'invokes once both requests are finished' do
90
+ user1 = MyUser.find 1
91
+ user2 = MyUser.find_by id: 2
92
+
93
+ expect do |cb|
94
+ Acfs.on(user1, user2, &cb)
95
+ Acfs.run
96
+ end.to yield_with_args(user1, be_nil)
97
+ end
98
+
99
+ it 'invokes once remaining requests are finished' do
100
+ user1 = MyUser.find 1
101
+ Acfs.run # Finish the first request
102
+
103
+ user2 = MyUser.find_by id: 2
104
+
105
+ expect do |cb|
106
+ Acfs.on(user1, user2, &cb)
107
+ Acfs.run
108
+ end.to yield_with_args(user1, be_nil)
109
+ end
110
+
111
+ it 'invokes immediately when all requests have already been finished' do
112
+ user1 = MyUser.find 1
113
+ user2 = MyUser.find_by id: 2
114
+ Acfs.run
115
+
116
+ expect do |cb|
117
+ Acfs.on(user1, user2, &cb)
118
+ end.to yield_with_args(user1, be_nil)
119
+ end
120
+ end
74
121
  end
75
122
 
76
123
  describe '#runner' do
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Acfs::Location do
6
+ let(:location) { described_class.new(uri, args) }
7
+ let(:uri) { 'http://localhost/users/:id' }
8
+ let(:args) { {id: 4} }
9
+
10
+ describe '#str' do
11
+ subject(:str) { location.str }
12
+
13
+ it 'replaces variables with values' do
14
+ expect(str).to eq 'http://localhost/users/4'
15
+ end
16
+
17
+ context 'with special characters' do
18
+ let(:args) { {id: '4 [@(\/!^$'} }
19
+
20
+ it 'escapes special characters' do
21
+ expect(str).to eq 'http://localhost/users/4+%5B%40%28%5C%2F%21%5E%24'
22
+ end
23
+ end
24
+ end
25
+ end