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,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Resource::Validation do
6
+ let(:params) { {name: 'john smith', age: 24} }
7
+ let(:model) { MyUserWithValidations.new params }
8
+
9
+ describe '#valid?' do
10
+ context 'with valid attributes' do
11
+ subject { model }
12
+
13
+ it { should be_valid }
14
+ end
15
+
16
+ context 'with invalid attributes' do
17
+ let(:params) { {name: 'invname'} }
18
+ subject { model }
19
+
20
+ it { should_not be_valid }
21
+ end
22
+
23
+ context 'on resource with service side errors' do
24
+ before { Acfs::Stub.enable }
25
+ after { Acfs::Stub.disable }
26
+
27
+ before do
28
+ Acfs::Stub.resource MyUser, :create, return: {errors: {name: ['can\'t be blank']}}, raise: 422
29
+ end
30
+
31
+ let(:params) { {} }
32
+ let(:resource) { MyUser.create params }
33
+ subject { resource }
34
+
35
+ it { should_not be_valid }
36
+
37
+ it 'should not override errors' do
38
+ subject.valid?
39
+ expect(subject.errors.to_hash).to eq(name: ['can\'t be blank'])
40
+ end
41
+ end
42
+ end
43
+
44
+ describe '#errors' do
45
+ context 'with valid attributes' do
46
+ let(:params) { {name: 'john smith', age: 24} }
47
+ before { model.valid? }
48
+ subject { model.errors }
49
+
50
+ it { should be_empty }
51
+ end
52
+
53
+ context 'with invalid attributes' do
54
+ let(:params) { {name: 'john'} }
55
+ before { model.valid? }
56
+ subject { model.errors }
57
+
58
+ it { should_not be_empty }
59
+ it { should have(2).items }
60
+
61
+ it 'should contain a list of error messages' do
62
+ expect(subject.to_hash).to eq age: ["can't be blank"], name: ['is invalid']
63
+ end
64
+ end
65
+
66
+ context 'server side errors' do
67
+ before { Acfs::Stub.enable }
68
+ after { Acfs::Stub.disable }
69
+
70
+ before do
71
+ Acfs::Stub.resource MyUser, :create,
72
+ with: {}, return: {errors: errors}, raise: 422
73
+ end
74
+
75
+ let(:params) { {} }
76
+ let(:resource) { MyUser.create params }
77
+ subject { resource.errors.to_hash }
78
+
79
+ context 'with `field => [messages]` payload' do
80
+ let(:errors) { {name: ['cannot be blank']} }
81
+
82
+ it { is_expected.to eq(name: ['cannot be blank']) }
83
+ end
84
+
85
+ context 'with `field => message` payload' do
86
+ let(:errors) { {name: 'cannot be blank'} }
87
+
88
+ it { is_expected.to eq(name: ['cannot be blank']) }
89
+ end
90
+
91
+ context 'with `[messages]` payload' do
92
+ let(:errors) { ['cannot be blank'] }
93
+
94
+ it { is_expected.to eq(base: ['cannot be blank']) }
95
+ end
96
+ end
97
+ end
98
+
99
+ describe '#save!' do
100
+ subject { -> { model.save! } }
101
+ before { allow(model).to receive(:operation) }
102
+
103
+ context 'with invalid attributes' do
104
+ let(:params) { {name: 'john'} }
105
+
106
+ it { expect { subject.call }.to raise_error Acfs::InvalidResource }
107
+ end
108
+
109
+ context 'on new resource' do
110
+ it 'should validate with `create` context' do
111
+ expect(model).to receive(:valid?).with(:create).and_call_original
112
+ subject.call
113
+ end
114
+ end
115
+
116
+ context 'on changed resource' do
117
+ before { model.loaded! }
118
+ let(:model) { super().tap {|m| m.id = 1 } }
119
+
120
+ it 'should validate with `save` context' do
121
+ expect(model).to receive(:valid?).with(:save).and_call_original
122
+ subject.call
123
+ end
124
+ end
125
+ end
126
+
127
+ describe 'validates with context' do
128
+ end
129
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Response::Formats do
6
+ let(:status) { 200 }
7
+ let(:mime_type) { 'application/unknown' }
8
+ let(:headers) { {'Content-Type' => mime_type} }
9
+ let(:request) { Acfs::Request.new 'fubar' }
10
+ let(:body) { nil }
11
+ let(:response) { Acfs::Response.new request, status: status, headers: headers, body: body }
12
+
13
+ context 'without Content-Type header' do
14
+ let(:headers) { {} }
15
+
16
+ it "should fallback on 'text/plain'" do
17
+ expect(response.content_type).to be == Mime[:text]
18
+ end
19
+ end
20
+
21
+ context 'with JSON mimetype' do
22
+ let(:mime_type) { 'application/json' }
23
+
24
+ describe '#content_type' do
25
+ it 'should return Mime::JSON' do
26
+ expect(response.content_type).to be == Mime[:json]
27
+ end
28
+ end
29
+
30
+ describe '#json?' do
31
+ it 'should return true' do
32
+ expect(response).to be_json
33
+ end
34
+ end
35
+
36
+ context 'with charset option' do
37
+ let(:mime_type) { 'application/json; charset=utf8' }
38
+
39
+ describe '#content_type' do
40
+ it 'should return Mime::JSON' do
41
+ expect(response.content_type).to be == Mime[:json]
42
+ end
43
+ end
44
+
45
+ describe '#json?' do
46
+ it 'should return true' do
47
+ expect(response).to be_json
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Response::Status do
6
+ let(:status) { 200 }
7
+ let(:mime_type) { 'application/unknown' }
8
+ let(:headers) { {'Content-Type' => mime_type} }
9
+ let(:request) { Acfs::Request.new 'fubar' }
10
+ let(:body) { nil }
11
+ let(:response) { Acfs::Response.new request, status: status, headers: headers, body: body }
12
+
13
+ describe '#status_code alias #code' do
14
+ context 'when given' do
15
+ let(:status) { 200 }
16
+
17
+ it 'should return status code' do
18
+ expect(response.code).to be == 200
19
+ expect(response.status_code).to be == 200
20
+ end
21
+ end
22
+
23
+ context 'when nil' do
24
+ let(:status) { nil }
25
+
26
+ it 'should return zero' do
27
+ expect(response.code).to be == 0
28
+ expect(response.status_code).to be == 0
29
+ end
30
+ end
31
+ end
32
+
33
+ describe '#success?' do
34
+ context 'with success status code' do
35
+ let(:status) { 200 }
36
+ it { expect(response).to be_success }
37
+ end
38
+
39
+ context 'with error status code' do
40
+ let(:status) { 500 }
41
+ it { expect(response).to_not be_success }
42
+ end
43
+
44
+ context 'with zero status code' do
45
+ let(:status) { nil }
46
+ it { expect(response).to_not be_success }
47
+ end
48
+ end
49
+
50
+ describe '#modified?' do
51
+ context 'with success status code' do
52
+ let(:status) { 200 }
53
+ it { expect(response).to be_modified }
54
+ end
55
+
56
+ context 'with not modified status code' do
57
+ let(:status) { 304 }
58
+ it { expect(response).to_not be_modified }
59
+ end
60
+
61
+ context 'with error status code' do
62
+ let(:status) { 500 }
63
+ it { expect(response).to be_modified }
64
+ end
65
+
66
+ context 'with zero status code' do
67
+ let(:status) { nil }
68
+ it { expect(response).to be_modified }
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class NullAdapter < Acfs::Adapter::Base
6
+ # Start processing queued requests.
7
+ #
8
+ def start; end
9
+
10
+ # Abort running and queued requests.
11
+ #
12
+ def abort; end
13
+
14
+ # Run request right now skipping queue.
15
+ #
16
+ def run(_); end
17
+
18
+ # Enqueue request to be run later.
19
+ #
20
+ def queue(_); end
21
+ end
22
+
23
+ class NotificationCollector
24
+ def call(*args)
25
+ events << ActiveSupport::Notifications::Event.new(*args)
26
+ end
27
+
28
+ def events
29
+ @events ||= []
30
+ end
31
+ end
32
+
33
+ describe ::Acfs::Runner do
34
+ let(:adapter) { ::NullAdapter.new }
35
+ let(:runner) { ::Acfs::Runner.new adapter }
36
+ let(:collector) { NotificationCollector.new }
37
+ let(:collector2) { NotificationCollector.new }
38
+
39
+ after do
40
+ ::ActiveSupport::Notifications.notifier = \
41
+ ::ActiveSupport::Notifications::Fanout.new
42
+ end
43
+
44
+ describe '#instrumentation' do
45
+ before do
46
+ ::ActiveSupport::Notifications.subscribe(/^acfs\.runner/, collector)
47
+ ::ActiveSupport::Notifications.subscribe(/^acfs\.operation/, collector2)
48
+ end
49
+
50
+ describe '#process' do
51
+ it 'should trigger event' do
52
+ runner.process ::Acfs::Operation.new MyUser, :read, params: {id: 0}
53
+ expect(collector.events).to have(1).items
54
+ expect(collector2.events).to have(1).items
55
+ end
56
+ end
57
+
58
+ describe '#run' do
59
+ it 'should trigger event' do
60
+ runner.run ::Acfs::Operation.new MyUser, :read, params: {id: 0}
61
+ expect(collector.events).to have(1).items
62
+ expect(collector2.events).to have(0).items
63
+ end
64
+ end
65
+
66
+ describe '#enqueue' do
67
+ it 'should trigger event' do
68
+ runner.enqueue ::Acfs::Operation.new MyUser, :read, params: {id: 0}
69
+ expect(collector.events).to have(1).items
70
+ expect(collector2.events).to have(0).items
71
+ end
72
+ end
73
+ end
74
+
75
+ describe '#run' do
76
+ before do
77
+ expect_any_instance_of(UserService).to receive(:prepare).and_return nil
78
+ end
79
+ it 'it should not do requests when a middleware aborted' do
80
+ expect(adapter).to_not receive :run
81
+ runner.run ::Acfs::Operation.new MyUser, :read, params: {id: 0}
82
+ end
83
+ end
84
+
85
+ describe '#enqueue' do
86
+ before do
87
+ expect_any_instance_of(UserService).to receive(:prepare).and_return nil
88
+ end
89
+ it 'it should not do requests when a middleware aborted' do
90
+ expect(adapter).to_not receive :queue
91
+ runner.enqueue ::Acfs::Operation.new MyUser, :read, params: {id: 0}
92
+ runner.start
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ class TestMiddleware < Acfs::Middleware::Base
6
+ end
7
+
8
+ describe Acfs::Service::Middleware do
9
+ let(:srv_class) { Class.new(Acfs::Service) }
10
+ let(:options) { {} }
11
+ let(:middleware) { TestMiddleware }
12
+
13
+ describe '.use' do
14
+ let(:options) { {abc: 'cde'} }
15
+
16
+ it 'should add middleware to list' do
17
+ srv_class.use middleware
18
+
19
+ expect(srv_class.middleware).to include(middleware)
20
+ end
21
+
22
+ it 'should add middleware to stack' do
23
+ srv_class.use middleware
24
+
25
+ expect(srv_class.middleware.build(1)).to be_a(middleware)
26
+ end
27
+
28
+ it 'should instantiate middleware object' do
29
+ expect(middleware).to receive(:new).with(anything, options)
30
+
31
+ srv_class.use middleware, options
32
+ srv_class.middleware.build
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::Service do
6
+ let(:srv_class) { Class.new(Acfs::Service) { identity :test } }
7
+ let(:options) { {} }
8
+ let(:service) { srv_class.new(**options) }
9
+
10
+ before do
11
+ Acfs::Configuration.current.locate :test, ''
12
+ end
13
+
14
+ describe '#initialize' do
15
+ let(:options) { {path: 'abc', key: 'value'} }
16
+
17
+ it 'should set options' do
18
+ expect(service.options).to eq(options)
19
+ end
20
+ end
21
+
22
+ describe '#location' do
23
+ let(:resource) { Class.new }
24
+ before { allow(resource).to receive(:location_default_path, &proc {|_a, p| p }) }
25
+
26
+ it 'should extract resource path name from given class' do
27
+ expect(service.location(resource).to_s).to eq('/classes')
28
+ end
29
+
30
+ context 'with path options' do
31
+ let(:options) { {path: 'abc'} }
32
+
33
+ it 'should have custom resource path' do
34
+ expect(service.location(resource).to_s).to eq('/abc')
35
+ end
36
+ end
37
+ end
38
+
39
+ describe '.base_url' do
40
+ before do
41
+ Acfs::Configuration.current.locate :test, 'http://abc.de/api/v1'
42
+ end
43
+
44
+ it 'should return configured URI for service' do
45
+ expect(srv_class.base_url).to eq('http://abc.de/api/v1')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Acfs::SingletonResource do
6
+ let(:model) { Single }
7
+
8
+ describe '.find' do
9
+ before do
10
+ stub_request(:get, 'http://users.example.org/singles')
11
+ .to_return response id: 1
12
+ end
13
+
14
+ let(:action) { ->(cb) { model.find(&cb) } }
15
+ it_should_behave_like 'a query method with multi-callback support'
16
+ end
17
+ end