syncano 4.0.0.alpha4 → 4.0.0.pre
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/.ruby-version +1 -1
- data/README.md +1 -13
- data/circle.yml +1 -1
- data/lib/active_attr/dirty.rb +26 -0
- data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
- data/lib/active_attr/typecasting_override.rb +29 -0
- data/lib/syncano.rb +9 -55
- data/lib/syncano/api.rb +2 -20
- data/lib/syncano/connection.rb +47 -48
- data/lib/syncano/model/associations.rb +121 -0
- data/lib/syncano/model/associations/base.rb +38 -0
- data/lib/syncano/model/associations/belongs_to.rb +30 -0
- data/lib/syncano/model/associations/has_many.rb +75 -0
- data/lib/syncano/model/associations/has_one.rb +22 -0
- data/lib/syncano/model/base.rb +257 -0
- data/lib/syncano/model/callbacks.rb +49 -0
- data/lib/syncano/model/scope_builder.rb +158 -0
- data/lib/syncano/query_builder.rb +7 -11
- data/lib/syncano/resources/base.rb +66 -91
- data/lib/syncano/schema.rb +159 -10
- data/lib/syncano/schema/attribute_definition.rb +0 -75
- data/lib/syncano/schema/resource_definition.rb +2 -24
- data/lib/syncano/version.rb +1 -1
- data/spec/integration/syncano_spec.rb +26 -268
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/connection_spec.rb +74 -34
- data/spec/unit/query_builder_spec.rb +2 -2
- data/spec/unit/resources_base_spec.rb +64 -125
- data/spec/unit/schema/resource_definition_spec.rb +3 -24
- data/spec/unit/schema_spec.rb +55 -5
- data/spec/unit/syncano_spec.rb +9 -45
- data/syncano.gemspec +0 -5
- metadata +14 -87
- data/lib/syncano/api/endpoints.rb +0 -17
- data/lib/syncano/poller.rb +0 -55
- data/lib/syncano/resources.rb +0 -158
- data/lib/syncano/resources/paths.rb +0 -48
- data/lib/syncano/resources/resource_invalid.rb +0 -15
- data/lib/syncano/response.rb +0 -55
- data/lib/syncano/schema/endpoints_whitelist.rb +0 -40
- data/lib/syncano/upload_io.rb +0 -7
- data/spec/unit/resources/paths_spec.rb +0 -21
- data/spec/unit/response_spec.rb +0 -75
- data/spec/unit/schema/attribute_definition_spec.rb +0 -18
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,6 @@ Dotenv.load
|
|
7
7
|
require 'rspec-prof' if ENV['SPEC_PROFILE']
|
8
8
|
require 'syncano'
|
9
9
|
require 'webmock/rspec'
|
10
|
-
require 'celluloid/test'
|
11
10
|
|
12
11
|
WebMock.disable_net_connect!
|
13
12
|
|
@@ -17,5 +16,4 @@ end
|
|
17
16
|
|
18
17
|
def endpoint_uri(path)
|
19
18
|
[Syncano::Connection.api_root,"v1", path].join("/")
|
20
|
-
end
|
21
|
-
|
19
|
+
end
|
@@ -7,68 +7,108 @@ describe Syncano::Connection do
|
|
7
7
|
specify { expect(described_class.new).to_not be_authenticated }
|
8
8
|
end
|
9
9
|
|
10
|
-
describe '#
|
11
|
-
|
10
|
+
describe '#request' do
|
11
|
+
let(:api_key) { '87a7da987da98sd7a98' }
|
12
12
|
|
13
|
-
|
14
|
-
let(:email) { 'kiszka@koza.com' }
|
15
|
-
let(:password) { 'kiszonka' }
|
16
|
-
let(:success_status) { 200 }
|
17
|
-
let(:unauthorized_status) { 401 }
|
13
|
+
subject { described_class.new(api_key: api_key) }
|
18
14
|
|
19
|
-
context '
|
15
|
+
context 'called with unsupported method' do
|
16
|
+
specify do
|
17
|
+
expect { subject.request :koza, 'fafarafa' }.
|
18
|
+
to raise_error(RuntimeError, 'Unsupported method "koza"')
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
context 'called with supported method' do
|
20
23
|
before do
|
21
|
-
|
22
|
-
with(:
|
23
|
-
|
24
|
+
stub_request(:get, endpoint_uri('somepath/')).
|
25
|
+
with(headers: {'X-Api-Key'=>'87a7da987da98sd7a98'}).
|
26
|
+
to_return(body: generate_body(some: 'response'))
|
24
27
|
end
|
25
28
|
|
26
29
|
specify do
|
27
|
-
expect
|
30
|
+
expect(subject.request(:get, 'somepath/')).to eq('some' => 'response')
|
28
31
|
end
|
29
32
|
end
|
30
33
|
|
31
|
-
context '
|
34
|
+
context 'called with supported method returning an error' do
|
32
35
|
before do
|
33
|
-
|
36
|
+
stub_request(:post, endpoint_uri('instances/')).
|
37
|
+
with(body: { 'name' => 'koza' },
|
38
|
+
headers: {'X-Api-Key'=>'87a7da987da98sd7a98'}).
|
39
|
+
to_return(body: generate_body({name: ['This field can not be "koza"']}),
|
40
|
+
status: 400)
|
41
|
+
end
|
34
42
|
|
43
|
+
specify do
|
44
|
+
expect { subject.request(:post, '/v1/instances/', { name: "koza" }) }.
|
45
|
+
to raise_error(Syncano::ClientError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'successful returning empty body' do
|
50
|
+
before do
|
51
|
+
stub_request(:delete, endpoint_uri('instances/kiszonka/')).
|
52
|
+
with(headers: {'X-Api-Key'=>'87a7da987da98sd7a98'}).
|
53
|
+
to_return(body: nil, status: 204)
|
35
54
|
end
|
36
55
|
|
37
56
|
specify do
|
38
|
-
expect { subject.
|
57
|
+
expect { subject.request(:delete, '/v1/instances/kiszonka/', {}) }.
|
58
|
+
to_not raise_error
|
39
59
|
end
|
60
|
+
|
40
61
|
end
|
41
62
|
end
|
42
63
|
|
43
|
-
describe '#
|
44
|
-
|
45
|
-
let(:api_key) { '87a7da987da98sd7a98' }
|
46
|
-
let(:connection_params) { { api_key: api_key, user_key: 'Us3rK3y' } }
|
47
|
-
let(:raw_response) { { body: 'koza' } }
|
48
|
-
let(:handled_response) { double :handled_response }
|
64
|
+
describe '#authenticate' do
|
65
|
+
subject { described_class.new }
|
49
66
|
|
50
|
-
|
67
|
+
let(:authenticate_uri) { endpoint_uri('account/auth/') }
|
68
|
+
let(:email) { 'kiszka@koza.com' }
|
69
|
+
let(:password) { 'kiszonka' }
|
70
|
+
let(:success_status) { 200 }
|
71
|
+
let(:unauthorized_status) { 401 }
|
51
72
|
|
52
|
-
context '
|
73
|
+
context 'successful' do
|
53
74
|
before do
|
54
|
-
stub_request(:
|
55
|
-
with(
|
75
|
+
stub_request(:post, authenticate_uri).
|
76
|
+
with(body: { 'email' => email, 'password' => password } ).
|
77
|
+
to_return(body: successful_body, status: success_status)
|
78
|
+
end
|
56
79
|
|
57
|
-
|
58
|
-
|
59
|
-
and_return(handled_response)
|
80
|
+
it 'should get an API key' do
|
81
|
+
expect { subject.authenticate(email, password) }.to change { subject.authenticated? }
|
60
82
|
end
|
61
83
|
|
62
|
-
|
63
|
-
|
84
|
+
def successful_body
|
85
|
+
generate_body id: 15,
|
86
|
+
email: email,
|
87
|
+
first_name: '',
|
88
|
+
last_name: '',
|
89
|
+
account_key: 'kozakoza123'
|
64
90
|
end
|
65
91
|
end
|
66
92
|
|
67
|
-
context '
|
68
|
-
|
69
|
-
|
70
|
-
|
93
|
+
context 'failed' do
|
94
|
+
before do
|
95
|
+
stub_request(:post, authenticate_uri).
|
96
|
+
with(body: { 'email' => email, 'password' => password }).
|
97
|
+
to_return(body: failed_body, status: unauthorized_status)
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'should raise an exception' do
|
102
|
+
expect { subject.authenticate(email, password) }.to raise_error(Syncano::ClientError)
|
103
|
+
end
|
104
|
+
|
105
|
+
def failed_body
|
106
|
+
generate_body detail: 'Invalid email or password.'
|
71
107
|
end
|
72
108
|
end
|
73
109
|
end
|
110
|
+
|
111
|
+
def generate_body(params)
|
112
|
+
JSON.generate params
|
113
|
+
end
|
74
114
|
end
|
@@ -23,14 +23,14 @@ describe Syncano::QueryBuilder do
|
|
23
23
|
|
24
24
|
describe '.first' do
|
25
25
|
specify do
|
26
|
-
expect(resource_class).to receive(:first).with(connection, scope_parameters
|
26
|
+
expect(resource_class).to receive(:first).with(connection, scope_parameters)
|
27
27
|
subject.first
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
31
|
describe '.last' do
|
32
32
|
specify do
|
33
|
-
expect(resource_class).to receive(:last).with(connection, scope_parameters
|
33
|
+
expect(resource_class).to receive(:last).with(connection, scope_parameters)
|
34
34
|
subject.last
|
35
35
|
end
|
36
36
|
end
|
@@ -5,90 +5,90 @@ require 'rspec/expectations'
|
|
5
5
|
|
6
6
|
describe Syncano::Resources::Base do
|
7
7
|
subject do
|
8
|
-
Syncano::
|
9
|
-
|
10
|
-
{ :attributes => { 'name' => { 'read_only' => false,
|
8
|
+
Syncano::Schema.send(:new_resource_class,
|
9
|
+
{:attributes => {'name' => {'read_only' => false,
|
11
10
|
'primary_key' => true,
|
12
11
|
'required' => true,
|
13
12
|
'label' => 'name',
|
14
13
|
'max_length' => 64,
|
15
|
-
'type' => 'string'
|
14
|
+
'type' => 'string'},
|
16
15
|
|
17
|
-
'created_at' => {
|
16
|
+
'created_at' => {'read_only' => true,
|
18
17
|
'required' => false,
|
19
18
|
'type' => 'datetime',
|
20
|
-
'label' => 'created at'
|
21
|
-
'updated_at' => {
|
19
|
+
'label' => 'created at'},
|
20
|
+
'updated_at' => {'read_only' => true,
|
22
21
|
'required' => false,
|
23
22
|
'type' => 'datetime',
|
24
|
-
'label' => 'updated at'
|
25
|
-
'role' => {
|
23
|
+
'label' => 'updated at'},
|
24
|
+
'role' => {'read_only' => true,
|
26
25
|
'required' => false,
|
27
|
-
'type' => 'field'
|
28
|
-
'owner' => {
|
26
|
+
'type' => 'field'},
|
27
|
+
'owner' => {'first_name' => {'read_only' => false,
|
29
28
|
'max_length' => 35,
|
30
29
|
'required' => false,
|
31
30
|
'type' => 'string',
|
32
|
-
'label' => 'first name'
|
33
|
-
'last_name' => {
|
31
|
+
'label' => 'first name'},
|
32
|
+
'last_name' => {'read_only' => false,
|
34
33
|
'max_length' => 35,
|
35
34
|
'required' => false,
|
36
35
|
'type' => 'string',
|
37
|
-
'label' => 'last name'
|
38
|
-
'id' => {
|
36
|
+
'label' => 'last name'},
|
37
|
+
'id' => {'read_only' => true,
|
39
38
|
'required' => false,
|
40
39
|
'type' => 'integer',
|
41
|
-
'label' => 'ID'
|
42
|
-
'email' => {
|
40
|
+
'label' => 'ID'},
|
41
|
+
'email' => {'read_only' => false,
|
43
42
|
'max_length' => 254,
|
44
43
|
'required' => true,
|
45
44
|
'type' => 'email',
|
46
|
-
'label' => 'email address'
|
47
|
-
'metadata' => {
|
45
|
+
'label' => 'email address'}},
|
46
|
+
'metadata' => {'read_only' => false,
|
48
47
|
'required' => false,
|
49
48
|
'type' => 'field',
|
50
|
-
'label' => 'metadata'
|
51
|
-
'description' => {
|
49
|
+
'label' => 'metadata'},
|
50
|
+
'description' => {'read_only' => false,
|
52
51
|
'required' => false,
|
53
52
|
'type' => 'string',
|
54
|
-
'label' => 'description'
|
55
|
-
:associations => {
|
53
|
+
'label' => 'description'}},
|
54
|
+
:associations => {'read_only' => true,
|
56
55
|
'required' => false,
|
57
56
|
'type' => 'links',
|
58
|
-
'links' => [{
|
59
|
-
'name' => 'self'
|
60
|
-
{
|
61
|
-
'name' => 'admins'
|
62
|
-
{
|
63
|
-
'name' => 'classes'
|
64
|
-
{
|
65
|
-
'name' => 'codeboxes'
|
66
|
-
{
|
67
|
-
'name' => 'runtimes'
|
68
|
-
{
|
69
|
-
'name' => 'invitations'
|
70
|
-
{
|
71
|
-
'name' => 'api_keys'
|
72
|
-
{
|
73
|
-
'name' => 'triggers'
|
74
|
-
{
|
75
|
-
'name' => 'webhooks'
|
76
|
-
:collection => {
|
77
|
-
|
57
|
+
'links' => [{'type' => 'detail',
|
58
|
+
'name' => 'self'},
|
59
|
+
{'type' => 'list',
|
60
|
+
'name' => 'admins'},
|
61
|
+
{'type' => 'list',
|
62
|
+
'name' => 'classes'},
|
63
|
+
{'type' => 'list',
|
64
|
+
'name' => 'codeboxes'},
|
65
|
+
{'type' => 'list',
|
66
|
+
'name' => 'runtimes'},
|
67
|
+
{'type' => 'list',
|
68
|
+
'name' => 'invitations'},
|
69
|
+
{'type' => 'list',
|
70
|
+
'name' => 'api_keys'},
|
71
|
+
{'type' => 'list',
|
72
|
+
'name' => 'triggers'},
|
73
|
+
{'type' => 'list',
|
74
|
+
'name' => 'webhooks'}]},
|
75
|
+
:collection => {:path => '/v1/instances/',
|
76
|
+
:http_methods => ['post',
|
78
77
|
'get'],
|
79
|
-
:params => []
|
80
|
-
:member => {
|
78
|
+
:params => []},
|
79
|
+
:member => {:path => '/v1/instances/{name}/',
|
81
80
|
:http_methods => ['put',
|
82
81
|
'get',
|
83
82
|
'patch',
|
84
83
|
'delete'],
|
85
|
-
:params => ['name']
|
86
|
-
:custom_methods => []
|
84
|
+
:params => ['name']},
|
85
|
+
:custom_methods => []},
|
86
|
+
'some_name'
|
87
87
|
)
|
88
88
|
end
|
89
89
|
|
90
|
-
let(:connection) { double('connection')
|
91
|
-
let(:scope_parameters) { double('scope_parameters')
|
90
|
+
let(:connection) { double('connection')}
|
91
|
+
let(:scope_parameters) { double('scope_parameters')}
|
92
92
|
|
93
93
|
describe '.find' do
|
94
94
|
let(:response) {
|
@@ -140,7 +140,7 @@ describe Syncano::Resources::Base do
|
|
140
140
|
end
|
141
141
|
|
142
142
|
it 'should init attributes' do
|
143
|
-
resource = subject.new(connection, {}, { name: 'test' })
|
143
|
+
resource = subject.new(connection, {}, { name: 'test' }, true)
|
144
144
|
expect(resource.name).to eq('test')
|
145
145
|
end
|
146
146
|
|
@@ -153,94 +153,33 @@ describe Syncano::Resources::Base do
|
|
153
153
|
resource = subject.new(connection, {}, { links: { self: '/v1/instances/test/' }, name: 'test' }, false)
|
154
154
|
expect(resource.changed?).to eq(true)
|
155
155
|
end
|
156
|
-
end
|
157
|
-
|
158
|
-
describe '#new_record?' do
|
159
|
-
let(:resource) { subject.new connection, {}, { name: 'asd' }, from_db }
|
160
|
-
|
161
|
-
context 'is true' do
|
162
|
-
let(:from_db) { false }
|
163
156
|
|
164
|
-
|
157
|
+
it 'should mark resource as not new if initialized with self path' do
|
158
|
+
resource = subject.new(connection, {}, { links: { self: '/v1/instances/test/' } }, true)
|
159
|
+
expect(resource.new_record?).to be(false)
|
165
160
|
end
|
166
161
|
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
specify { expect(resource.new_record?).to eq(false) }
|
162
|
+
it 'should mark resource as new if initialized without self path' do
|
163
|
+
resource = subject.new(connection, {}, { name: 'test' }, true)
|
164
|
+
expect(resource.new_record?).to be(true)
|
171
165
|
end
|
172
166
|
end
|
173
167
|
|
174
|
-
describe
|
175
|
-
|
176
|
-
before { expect(resource).to receive(:valid?) { false } }
|
177
|
-
end
|
178
|
-
|
179
|
-
shared_context "resource valid" do
|
180
|
-
before do
|
181
|
-
# expect(instance_of(Syncano::Resource::Base)).to receive(:valid?) { true }
|
182
|
-
|
183
|
-
expect(resource).to receive(:valid?) { true }
|
184
|
-
|
185
|
-
expect(connection).to receive(:request).
|
186
|
-
with(instance_of(Symbol), instance_of(String), instance_of(Hash)).
|
187
|
-
and_return({})
|
188
|
-
end
|
189
|
-
end
|
190
|
-
|
191
|
-
let(:resource) { subject.new connection, {}, {}, false }
|
168
|
+
describe '.create' do
|
169
|
+
it 'should create new object in Syncano' do
|
192
170
|
|
193
|
-
describe ".create!" do
|
194
|
-
before do
|
195
|
-
expect(subject).to receive(:new).and_return(resource)
|
196
|
-
expect(resource).to receive(:save!).and_return(resource)
|
197
|
-
end
|
198
|
-
|
199
|
-
specify { expect(subject.create!(connection, {}, {})).to eq(resource) }
|
200
171
|
end
|
172
|
+
end
|
201
173
|
|
202
|
-
|
203
|
-
|
204
|
-
expect(subject).to receive(:new).and_return(resource)
|
205
|
-
expect(resource).to receive(:save).and_return(false)
|
206
|
-
end
|
207
|
-
|
208
|
-
specify { expect(subject.create(connection, {}, {})).to eq(resource) }
|
209
|
-
specify { expect(subject.create(connection, {}, {}).new_record?).to eq(true) }
|
210
|
-
end
|
211
|
-
|
212
|
-
describe "#save" do
|
213
|
-
context "when invalid" do
|
214
|
-
include_context "resource invalid"
|
215
|
-
|
216
|
-
specify { expect(resource.save).to eq(false) }
|
217
|
-
end
|
218
|
-
|
219
|
-
context "when valid" do
|
220
|
-
include_context "resource valid"
|
174
|
+
describe '.update_attributes' do
|
175
|
+
it 'should create update object\'s attributes in Syncano' do
|
221
176
|
|
222
|
-
specify { expect(resource.save).to be_kind_of(Syncano::Resources::Base) }
|
223
|
-
end
|
224
177
|
end
|
178
|
+
end
|
225
179
|
|
226
|
-
|
227
|
-
|
228
|
-
include_context "resource invalid"
|
229
|
-
|
230
|
-
specify do
|
231
|
-
expect {
|
232
|
-
resource.save!
|
233
|
-
}.to raise_error(Syncano::Resources::ResourceInvalid)
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
context "when valid" do
|
238
|
-
include_context "resource valid"
|
180
|
+
describe '.destroy' do
|
181
|
+
it 'should delete object from Syncano' do
|
239
182
|
|
240
|
-
specify do
|
241
|
-
expect(resource.save!).to be_kind_of(Syncano::Resources::Base)
|
242
|
-
end
|
243
|
-
end
|
244
183
|
end
|
245
184
|
end
|
246
185
|
end
|
@@ -1,35 +1,14 @@
|
|
1
1
|
require_relative '../../spec_helper'
|
2
2
|
|
3
3
|
describe Syncano::Schema::ResourceDefinition do
|
4
|
-
let(:attribute) { double 'attribute' }
|
5
|
-
let(:definition) do
|
6
|
-
described_class.new(name, attributes: { 'koza' => raw_attribute },
|
7
|
-
associations: {
|
8
|
-
'links' => [{ 'name' => 'koza', 'type' => 'detail' }]
|
9
|
-
})
|
10
|
-
end
|
11
|
-
let(:name) { 'Kiszka' }
|
12
4
|
let(:raw_attribute) { double 'raw attribute' }
|
5
|
+
let(:attribute) { double 'attribute' }
|
13
6
|
|
14
7
|
before do
|
15
8
|
expect(Syncano::Schema::AttributeDefinition).to receive(:new).with('koza', raw_attribute).and_return(attribute)
|
16
9
|
end
|
17
10
|
|
18
|
-
|
19
|
-
|
20
|
-
specify { expect(definition.name).to eq(name) }
|
21
|
-
|
22
|
-
it 'should delete colliding links' do
|
23
|
-
expect(definition[:associations]['links']).to be_empty
|
24
|
-
end
|
25
|
-
|
26
|
-
describe 'top_level?' do
|
27
|
-
context "when is top level" do
|
28
|
-
pending "to be done"
|
29
|
-
end
|
30
|
-
|
31
|
-
context "when is not top level" do
|
32
|
-
pending "to be done"
|
33
|
-
end
|
11
|
+
it 'should create AttributeDefinition objects' do
|
12
|
+
expect(described_class.new(attributes: { 'koza' => raw_attribute }).attributes).to eq([attribute])
|
34
13
|
end
|
35
14
|
end
|