acfs 1.3.3 → 1.6.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 (96) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +372 -0
  3. data/LICENSE +22 -0
  4. data/README.md +321 -0
  5. data/acfs.gemspec +38 -0
  6. data/lib/acfs.rb +51 -0
  7. data/lib/acfs/adapter/base.rb +26 -0
  8. data/lib/acfs/adapter/typhoeus.rb +82 -0
  9. data/lib/acfs/collection.rb +28 -0
  10. data/lib/acfs/collections/paginatable.rb +76 -0
  11. data/lib/acfs/configuration.rb +120 -0
  12. data/lib/acfs/errors.rb +147 -0
  13. data/lib/acfs/global.rb +101 -0
  14. data/lib/acfs/location.rb +76 -0
  15. data/lib/acfs/middleware/base.rb +24 -0
  16. data/lib/acfs/middleware/json.rb +31 -0
  17. data/lib/acfs/middleware/logger.rb +23 -0
  18. data/lib/acfs/middleware/msgpack.rb +32 -0
  19. data/lib/acfs/middleware/print.rb +23 -0
  20. data/lib/acfs/middleware/serializer.rb +41 -0
  21. data/lib/acfs/operation.rb +96 -0
  22. data/lib/acfs/request.rb +32 -0
  23. data/lib/acfs/request/callbacks.rb +54 -0
  24. data/lib/acfs/resource.rb +39 -0
  25. data/lib/acfs/resource/attributes.rb +270 -0
  26. data/lib/acfs/resource/attributes/base.rb +29 -0
  27. data/lib/acfs/resource/attributes/boolean.rb +39 -0
  28. data/lib/acfs/resource/attributes/date_time.rb +32 -0
  29. data/lib/acfs/resource/attributes/dict.rb +39 -0
  30. data/lib/acfs/resource/attributes/float.rb +33 -0
  31. data/lib/acfs/resource/attributes/integer.rb +29 -0
  32. data/lib/acfs/resource/attributes/list.rb +36 -0
  33. data/lib/acfs/resource/attributes/string.rb +26 -0
  34. data/lib/acfs/resource/attributes/uuid.rb +48 -0
  35. data/lib/acfs/resource/dirty.rb +37 -0
  36. data/lib/acfs/resource/initialization.rb +31 -0
  37. data/lib/acfs/resource/loadable.rb +35 -0
  38. data/lib/acfs/resource/locatable.rb +135 -0
  39. data/lib/acfs/resource/operational.rb +26 -0
  40. data/lib/acfs/resource/persistence.rb +258 -0
  41. data/lib/acfs/resource/query_methods.rb +266 -0
  42. data/lib/acfs/resource/service.rb +44 -0
  43. data/lib/acfs/resource/validation.rb +49 -0
  44. data/lib/acfs/response.rb +30 -0
  45. data/lib/acfs/response/formats.rb +27 -0
  46. data/lib/acfs/response/status.rb +33 -0
  47. data/lib/acfs/rspec.rb +13 -0
  48. data/lib/acfs/runner.rb +102 -0
  49. data/lib/acfs/service.rb +94 -0
  50. data/lib/acfs/service/middleware.rb +58 -0
  51. data/lib/acfs/service/middleware/stack.rb +65 -0
  52. data/lib/acfs/singleton_resource.rb +85 -0
  53. data/lib/acfs/stub.rb +199 -0
  54. data/lib/acfs/util.rb +22 -0
  55. data/lib/acfs/version.rb +16 -0
  56. data/lib/acfs/yard.rb +6 -0
  57. data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
  58. data/spec/acfs/collection_spec.rb +157 -0
  59. data/spec/acfs/configuration_spec.rb +53 -0
  60. data/spec/acfs/global_spec.rb +140 -0
  61. data/spec/acfs/location_spec.rb +25 -0
  62. data/spec/acfs/middleware/json_spec.rb +79 -0
  63. data/spec/acfs/middleware/msgpack_spec.rb +62 -0
  64. data/spec/acfs/operation_spec.rb +12 -0
  65. data/spec/acfs/request/callbacks_spec.rb +48 -0
  66. data/spec/acfs/request_spec.rb +79 -0
  67. data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
  68. data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
  69. data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
  70. data/spec/acfs/resource/attributes/float_spec.rb +61 -0
  71. data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
  72. data/spec/acfs/resource/attributes/list_spec.rb +60 -0
  73. data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
  74. data/spec/acfs/resource/attributes_spec.rb +179 -0
  75. data/spec/acfs/resource/dirty_spec.rb +49 -0
  76. data/spec/acfs/resource/initialization_spec.rb +36 -0
  77. data/spec/acfs/resource/loadable_spec.rb +22 -0
  78. data/spec/acfs/resource/locatable_spec.rb +118 -0
  79. data/spec/acfs/resource/persistance_spec.rb +322 -0
  80. data/spec/acfs/resource/query_methods_spec.rb +548 -0
  81. data/spec/acfs/resource/validation_spec.rb +129 -0
  82. data/spec/acfs/response/formats_spec.rb +52 -0
  83. data/spec/acfs/response/status_spec.rb +71 -0
  84. data/spec/acfs/runner_spec.rb +95 -0
  85. data/spec/acfs/service/middleware_spec.rb +35 -0
  86. data/spec/acfs/service_spec.rb +48 -0
  87. data/spec/acfs/singleton_resource_spec.rb +17 -0
  88. data/spec/acfs/stub_spec.rb +345 -0
  89. data/spec/acfs_spec.rb +205 -0
  90. data/spec/fixtures/config.yml +14 -0
  91. data/spec/spec_helper.rb +42 -0
  92. data/spec/support/hash.rb +11 -0
  93. data/spec/support/response.rb +12 -0
  94. data/spec/support/service.rb +92 -0
  95. data/spec/support/shared/find_callbacks.rb +50 -0
  96. metadata +159 -26
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Middleware::MessagePack do
6
+ let(:data) { [{id: 1, name: 'Anon'}, {id: 2, name: 'John', friends: [1]}] }
7
+ let(:body) { '' }
8
+ let(:headers) { {} }
9
+ let(:request) { Acfs::Request.new 'url', data: data }
10
+ let(:response) { Acfs::Response.new request, status: 200, headers: headers, body: body }
11
+ let(:decoder) { Acfs::Middleware::MessagePack.new ->(req) { req } }
12
+
13
+ before do
14
+ decoder.call request
15
+ end
16
+
17
+ describe 'encode' do
18
+ context 'with not serialized request' do
19
+ it 'should set Content-Type' do
20
+ expect(request.headers['Content-Type']).to eq 'application/x-msgpack'
21
+ end
22
+
23
+ it 'should append Accept header' do
24
+ expect(request.headers['Accept']).to eq 'application/x-msgpack;q=1'
25
+ end
26
+
27
+ context 'with JSON chained' do
28
+ let(:decoder) { Acfs::Middleware::JSON.new super(), q: 0.5 }
29
+
30
+ it 'should append to Accept header' do
31
+ expect(request.headers['Accept']).to eq 'application/json;q=0.5,application/x-msgpack;q=1'
32
+ end
33
+ end
34
+
35
+ it 'should serialize data to MessagePack' do
36
+ expect(MessagePack.unpack(request.body)).to eq data.map(&:stringify_keys)
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'with Message Pack response' do
42
+ let(:headers) { {'Content-Type' => 'application/x-msgpack'} }
43
+ let(:body) { MessagePack.pack data }
44
+
45
+ it 'should decode body data' do
46
+ request.complete! response
47
+
48
+ expect(response.data).to be == data.map(&:stringify_keys)
49
+ end
50
+ end
51
+
52
+ context 'without Message Pack response' do
53
+ let(:headers) { {'Content-Type' => 'application/text'} }
54
+ let(:body) { data.to_json }
55
+
56
+ it 'should not decode non-MessagePack encoded responses' do
57
+ request.complete! response
58
+
59
+ expect(response.data).to be_nil
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe ::Acfs::Operation do
6
+ let(:operation) { described_class.new MyUser, :read, params: {id: 0} }
7
+
8
+ context '#request' do
9
+ subject { operation.request }
10
+ its(:operation) { should eq operation }
11
+ end
12
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Request::Callbacks do
6
+ let(:callback) { ->(_res) {} }
7
+ let(:request) { Acfs::Request.new('fubar') }
8
+
9
+ describe '#on_complete' do
10
+ it 'should store a given callback' do
11
+ request.on_complete(&callback)
12
+
13
+ expect(request.callbacks).to have(1).item
14
+ expect(request.callbacks[0]).to be == callback
15
+ end
16
+
17
+ it 'should store multiple callback' do
18
+ request.on_complete {|_res| 'abc' }
19
+ request.on_complete(&callback)
20
+
21
+ expect(request.callbacks).to have(2).item
22
+ expect(request.callbacks[0]).to be == callback
23
+ end
24
+ end
25
+
26
+ describe '#complete!' do
27
+ let(:response) { Acfs::Response.new(request) }
28
+
29
+ it 'should trigger registered callbacks with given response' do
30
+ expect(callback).to receive(:call).with(response, kind_of(Proc))
31
+
32
+ request.on_complete(&callback)
33
+ request.complete! response
34
+ end
35
+
36
+ it 'should trigger multiple callback in reverted insertion order' do
37
+ check = []
38
+
39
+ request.on_complete {|res, nxt| check << 1; nxt.call res }
40
+ request.on_complete {|res, nxt| check << 2; nxt.call res }
41
+ request.on_complete {|res, nxt| check << 3; nxt.call res }
42
+
43
+ request.complete! response
44
+
45
+ expect(check).to be == [3, 2, 1]
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Request do
6
+ let(:url) { 'http://api.example.org/v1/examples' }
7
+ let(:headers) { nil }
8
+ let(:params) { nil }
9
+ let(:data) { nil }
10
+ let(:method) { :get }
11
+ let(:options) { {method: method, headers: headers, params: params, data: data} }
12
+ let(:request) { Acfs::Request.new(url, **options) }
13
+
14
+ describe '#url' do
15
+ it 'should return request URL' do
16
+ expect(request.url).to be == url
17
+ end
18
+
19
+ context 'with parameters' do
20
+ let(:params) { {id: 10} }
21
+
22
+ it 'should return URL without query' do
23
+ expect(request.url).to be == url.to_s
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '#headers' do
29
+ let(:headers) { {'Accept' => 'application/json'} }
30
+
31
+ it 'should return request headers' do
32
+ expect(request.headers).to be == headers
33
+ end
34
+ end
35
+
36
+ describe '#method' do
37
+ context 'when nil given' do
38
+ let(:method) { nil }
39
+
40
+ it 'should default to :get' do
41
+ expect(request.method).to be == :get
42
+ end
43
+ end
44
+
45
+ it 'should return request method' do
46
+ expect(request.method).to be == method
47
+ end
48
+ end
49
+
50
+ describe '#params' do
51
+ let(:params) { {id: 10} }
52
+
53
+ it 'should return request headers' do
54
+ expect(request.params).to be == params
55
+ end
56
+ end
57
+
58
+ describe '#data' do
59
+ let(:data) { {id: 10, name: 'Anon'} }
60
+
61
+ it 'should return request data' do
62
+ expect(request.data).to be == data
63
+ end
64
+ end
65
+
66
+ describe '#data' do
67
+ context 'with data' do
68
+ let(:data) { {id: 10, name: 'Anon'} }
69
+
70
+ it { expect(request).to be_data }
71
+ end
72
+
73
+ context 'without data' do
74
+ let(:data) { nil }
75
+
76
+ it { expect(request).to_not be_data }
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::Boolean do
6
+ subject { Acfs::Resource::Attributes::Boolean.new }
7
+
8
+ describe '#cast' do
9
+ it 'casts nil' do
10
+ expect(subject.cast(nil)).to eq nil
11
+ end
12
+
13
+ it 'casts empty string to false' do
14
+ expect(subject.cast('')).to eq nil
15
+ end
16
+
17
+ it 'casts blank string to false' do
18
+ expect(subject.cast(" \t")).to eq nil
19
+ end
20
+
21
+ it 'preserves boolean values' do
22
+ expect(subject.cast(false)).to eq false
23
+ expect(subject.cast(true)).to eq true
24
+ end
25
+
26
+ it 'casts falsy values to false' do
27
+ expect(subject.cast(false)).to eq false
28
+ expect(subject.cast(0)).to eq false
29
+ expect(subject.cast('0')).to eq false
30
+ expect(subject.cast('no')).to eq false
31
+ expect(subject.cast('NO')).to eq false
32
+ expect(subject.cast('off')).to eq false
33
+ expect(subject.cast('OFF')).to eq false
34
+ expect(subject.cast('false')).to eq false
35
+ expect(subject.cast('FALSE')).to eq false
36
+ expect(subject.cast('f')).to eq false
37
+ expect(subject.cast('F')).to eq false
38
+ end
39
+
40
+ it 'casts any other value to true' do
41
+ expect(subject.cast(true)).to eq true
42
+ expect(subject.cast(1)).to eq true
43
+ expect(subject.cast('1')).to eq true
44
+ expect(subject.cast('yes')).to eq true
45
+ expect(subject.cast('YES')).to eq true
46
+ expect(subject.cast('on')).to eq true
47
+ expect(subject.cast('ON')).to eq true
48
+ expect(subject.cast('true')).to eq true
49
+ expect(subject.cast('TRUE')).to eq true
50
+ expect(subject.cast('t')).to eq true
51
+ expect(subject.cast('T')).to eq true
52
+
53
+ expect(subject.cast(2)).to eq true
54
+ expect(subject.cast('wrong')).to eq true
55
+ expect(subject.cast('random')).to eq true
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::DateTime do
6
+ let(:type) { Acfs::Resource::Attributes::DateTime.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast value } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with empty string' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq nil }
19
+ end
20
+
21
+ context 'with blank string' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq nil }
24
+ end
25
+
26
+ context 'with DateTime' do
27
+ let(:value) { DateTime.now }
28
+ it { expect(subject.call).to eq value }
29
+ end
30
+
31
+ context 'with Time' do
32
+ let(:value) { Time.now }
33
+ it { expect(subject.call).to eq value.to_datetime }
34
+ end
35
+
36
+ context 'with Date' do
37
+ let(:value) { Date.today }
38
+ it { expect(subject.call).to eq value.to_datetime }
39
+ end
40
+
41
+ context 'with ISO8601' do
42
+ let(:value) { DateTime.now.iso8601 }
43
+ it { expect(subject.call.iso8601).to eq value }
44
+ end
45
+
46
+ context 'with invalid string' do
47
+ let(:value) { 'qwe123' }
48
+ it { is_expected.to raise_error ArgumentError }
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::Dict do
6
+ let(:type) { Acfs::Resource::Attributes::Dict.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast value } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with blank string (I)' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq({}) }
19
+ end
20
+
21
+ context 'with blank string (II)' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq({}) }
24
+ end
25
+
26
+ context 'with hash' do
27
+ let(:value) { {3 => true, abc: 4} }
28
+ it { expect(subject.call).to eq value }
29
+ end
30
+
31
+ context 'with non hashable object' do
32
+ let(:value) { Object.new }
33
+ it { is_expected.to raise_error TypeError }
34
+ end
35
+
36
+ context 'with hashable object (I)' do
37
+ let(:value) do
38
+ Class.new do
39
+ def to_hash
40
+ {id: object_id}
41
+ end
42
+ end.new
43
+ end
44
+
45
+ it { expect(subject.call).to eq id: value.object_id }
46
+ end
47
+
48
+ context 'with hashable object (II)' do
49
+ let(:value) do
50
+ Class.new do
51
+ def to_h
52
+ {id: object_id}
53
+ end
54
+ end.new
55
+ end
56
+
57
+ it { expect(subject.call).to eq id: value.object_id }
58
+ end
59
+
60
+ context 'with serializable object' do
61
+ let(:value) do
62
+ Class.new do
63
+ def serializable_hash
64
+ {id: object_id}
65
+ end
66
+ end.new
67
+ end
68
+
69
+ it { expect(subject.call).to eq id: value.object_id }
70
+ end
71
+
72
+ context 'with hash subclass object' do
73
+ let(:value) { HashWithIndifferentAccess.new test: :foo }
74
+ it { expect(subject.call).to be value }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Attributes::Float do
6
+ let(:type) { Acfs::Resource::Attributes::Float.new }
7
+
8
+ describe '#cast' do
9
+ subject { -> { type.cast value } }
10
+
11
+ context 'with nil' do
12
+ let(:value) { nil }
13
+ it { expect(subject.call).to eq nil }
14
+ end
15
+
16
+ context 'with blank string (I)' do
17
+ let(:value) { '' }
18
+ it { expect(subject.call).to eq 0.0 }
19
+ end
20
+
21
+ context 'with blank string (II)' do
22
+ let(:value) { " \t" }
23
+ it { expect(subject.call).to eq 0.0 }
24
+ end
25
+
26
+ context 'with float' do
27
+ let(:value) { 1.7 }
28
+ it { expect(subject.call).to eq 1.7 }
29
+ end
30
+
31
+ context 'with Infinity' do
32
+ let(:value) { 'Infinity' }
33
+ it { expect(subject.call).to eq ::Float::INFINITY }
34
+ end
35
+
36
+ context 'with -Infinity' do
37
+ let(:value) { '-Infinity' }
38
+ it { expect(subject.call).to eq(-::Float::INFINITY) }
39
+ end
40
+
41
+ context 'with NaN' do
42
+ let(:value) { 'NaN' }
43
+ it { expect(subject.call).to be_nan }
44
+ end
45
+
46
+ context 'with fixnum' do
47
+ let(:value) { 1 }
48
+ it { expect(subject.call).to eq 1.0 }
49
+ end
50
+
51
+ context 'with valid string' do
52
+ let(:value) { '1.7' }
53
+ it { expect(subject.call).to eq 1.7 }
54
+ end
55
+
56
+ context 'with invalid string (I)' do
57
+ let(:value) { '1.7a' }
58
+ it { is_expected.to raise_error ArgumentError }
59
+ end
60
+ end
61
+ end