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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +372 -0
- data/LICENSE +22 -0
- data/README.md +321 -0
- data/acfs.gemspec +38 -0
- data/lib/acfs.rb +51 -0
- data/lib/acfs/adapter/base.rb +26 -0
- data/lib/acfs/adapter/typhoeus.rb +82 -0
- data/lib/acfs/collection.rb +28 -0
- data/lib/acfs/collections/paginatable.rb +76 -0
- data/lib/acfs/configuration.rb +120 -0
- data/lib/acfs/errors.rb +147 -0
- data/lib/acfs/global.rb +101 -0
- data/lib/acfs/location.rb +76 -0
- data/lib/acfs/middleware/base.rb +24 -0
- data/lib/acfs/middleware/json.rb +31 -0
- data/lib/acfs/middleware/logger.rb +23 -0
- data/lib/acfs/middleware/msgpack.rb +32 -0
- data/lib/acfs/middleware/print.rb +23 -0
- data/lib/acfs/middleware/serializer.rb +41 -0
- data/lib/acfs/operation.rb +96 -0
- data/lib/acfs/request.rb +32 -0
- data/lib/acfs/request/callbacks.rb +54 -0
- data/lib/acfs/resource.rb +39 -0
- data/lib/acfs/resource/attributes.rb +270 -0
- data/lib/acfs/resource/attributes/base.rb +29 -0
- data/lib/acfs/resource/attributes/boolean.rb +39 -0
- data/lib/acfs/resource/attributes/date_time.rb +32 -0
- data/lib/acfs/resource/attributes/dict.rb +39 -0
- data/lib/acfs/resource/attributes/float.rb +33 -0
- data/lib/acfs/resource/attributes/integer.rb +29 -0
- data/lib/acfs/resource/attributes/list.rb +36 -0
- data/lib/acfs/resource/attributes/string.rb +26 -0
- data/lib/acfs/resource/attributes/uuid.rb +48 -0
- data/lib/acfs/resource/dirty.rb +37 -0
- data/lib/acfs/resource/initialization.rb +31 -0
- data/lib/acfs/resource/loadable.rb +35 -0
- data/lib/acfs/resource/locatable.rb +135 -0
- data/lib/acfs/resource/operational.rb +26 -0
- data/lib/acfs/resource/persistence.rb +258 -0
- data/lib/acfs/resource/query_methods.rb +266 -0
- data/lib/acfs/resource/service.rb +44 -0
- data/lib/acfs/resource/validation.rb +49 -0
- data/lib/acfs/response.rb +30 -0
- data/lib/acfs/response/formats.rb +27 -0
- data/lib/acfs/response/status.rb +33 -0
- data/lib/acfs/rspec.rb +13 -0
- data/lib/acfs/runner.rb +102 -0
- data/lib/acfs/service.rb +94 -0
- data/lib/acfs/service/middleware.rb +58 -0
- data/lib/acfs/service/middleware/stack.rb +65 -0
- data/lib/acfs/singleton_resource.rb +85 -0
- data/lib/acfs/stub.rb +199 -0
- data/lib/acfs/util.rb +22 -0
- data/lib/acfs/version.rb +16 -0
- data/lib/acfs/yard.rb +6 -0
- data/spec/acfs/adapter/typhoeus_spec.rb +55 -0
- data/spec/acfs/collection_spec.rb +157 -0
- data/spec/acfs/configuration_spec.rb +53 -0
- data/spec/acfs/global_spec.rb +140 -0
- data/spec/acfs/location_spec.rb +25 -0
- data/spec/acfs/middleware/json_spec.rb +79 -0
- data/spec/acfs/middleware/msgpack_spec.rb +62 -0
- data/spec/acfs/operation_spec.rb +12 -0
- data/spec/acfs/request/callbacks_spec.rb +48 -0
- data/spec/acfs/request_spec.rb +79 -0
- data/spec/acfs/resource/attributes/boolean_spec.rb +58 -0
- data/spec/acfs/resource/attributes/date_time_spec.rb +51 -0
- data/spec/acfs/resource/attributes/dict_spec.rb +77 -0
- data/spec/acfs/resource/attributes/float_spec.rb +61 -0
- data/spec/acfs/resource/attributes/integer_spec.rb +36 -0
- data/spec/acfs/resource/attributes/list_spec.rb +60 -0
- data/spec/acfs/resource/attributes/uuid_spec.rb +42 -0
- data/spec/acfs/resource/attributes_spec.rb +179 -0
- data/spec/acfs/resource/dirty_spec.rb +49 -0
- data/spec/acfs/resource/initialization_spec.rb +36 -0
- data/spec/acfs/resource/loadable_spec.rb +22 -0
- data/spec/acfs/resource/locatable_spec.rb +118 -0
- data/spec/acfs/resource/persistance_spec.rb +322 -0
- data/spec/acfs/resource/query_methods_spec.rb +548 -0
- data/spec/acfs/resource/validation_spec.rb +129 -0
- data/spec/acfs/response/formats_spec.rb +52 -0
- data/spec/acfs/response/status_spec.rb +71 -0
- data/spec/acfs/runner_spec.rb +95 -0
- data/spec/acfs/service/middleware_spec.rb +35 -0
- data/spec/acfs/service_spec.rb +48 -0
- data/spec/acfs/singleton_resource_spec.rb +17 -0
- data/spec/acfs/stub_spec.rb +345 -0
- data/spec/acfs_spec.rb +205 -0
- data/spec/fixtures/config.yml +14 -0
- data/spec/spec_helper.rb +42 -0
- data/spec/support/hash.rb +11 -0
- data/spec/support/response.rb +12 -0
- data/spec/support/service.rb +92 -0
- data/spec/support/shared/find_callbacks.rb +50 -0
- 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
|