acfs 1.3.0 → 1.4.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 (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