syncano 4.0.0.alpha1 → 4.0.0.alpha2
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/README.md +8 -157
- data/circle.yml +1 -1
- data/lib/syncano.rb +38 -11
- data/lib/syncano/api.rb +20 -2
- data/lib/syncano/api/endpoints.rb +17 -0
- data/lib/syncano/connection.rb +46 -54
- data/lib/syncano/poller.rb +55 -0
- data/lib/syncano/resources.rb +48 -16
- data/lib/syncano/resources/base.rb +46 -56
- data/lib/syncano/resources/paths.rb +48 -0
- data/lib/syncano/resources/resource_invalid.rb +15 -0
- data/lib/syncano/response.rb +55 -0
- data/lib/syncano/schema.rb +10 -29
- data/lib/syncano/schema/attribute_definition.rb +2 -2
- data/lib/syncano/schema/endpoints_whitelist.rb +40 -0
- data/lib/syncano/schema/resource_definition.rb +5 -0
- data/lib/syncano/upload_io.rb +7 -0
- data/lib/syncano/version.rb +1 -1
- data/spec/integration/syncano_spec.rb +220 -15
- data/spec/spec_helper.rb +3 -1
- data/spec/unit/connection_spec.rb +34 -97
- data/spec/unit/resources/paths_spec.rb +21 -0
- data/spec/unit/resources_base_spec.rb +77 -16
- data/spec/unit/response_spec.rb +75 -0
- data/spec/unit/schema/resource_definition_spec.rb +10 -0
- data/spec/unit/schema_spec.rb +5 -55
- data/syncano.gemspec +4 -0
- metadata +69 -13
- data/lib/active_attr/dirty.rb +0 -26
- data/lib/active_attr/typecasting/hash_typecaster.rb +0 -34
- data/lib/active_attr/typecasting_override.rb +0 -29
- data/lib/syncano/model/associations.rb +0 -121
- data/lib/syncano/model/associations/base.rb +0 -38
- data/lib/syncano/model/associations/belongs_to.rb +0 -30
- data/lib/syncano/model/associations/has_many.rb +0 -75
- data/lib/syncano/model/associations/has_one.rb +0 -22
- data/lib/syncano/model/base.rb +0 -257
- data/lib/syncano/model/callbacks.rb +0 -49
- data/lib/syncano/model/scope_builder.rb +0 -158
@@ -7,131 +7,68 @@ describe Syncano::Connection do
|
|
7
7
|
specify { expect(described_class.new).to_not be_authenticated }
|
8
8
|
end
|
9
9
|
|
10
|
-
describe '#
|
11
|
-
|
12
|
-
let(:api_key) { '87a7da987da98sd7a98' }
|
13
|
-
|
14
|
-
subject { described_class.new(api_key: api_key) }
|
15
|
-
|
16
|
-
context 'called with unsupported method' do
|
17
|
-
specify do
|
18
|
-
expect { subject.request :koza, 'fafarafa' }.
|
19
|
-
to raise_error(RuntimeError, 'Unsupported method "koza"')
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
context 'called with supported method' do
|
24
|
-
before do
|
25
|
-
stub_request(:get, endpoint_uri('somepath/')).
|
26
|
-
with(headers: headers).
|
27
|
-
to_return(body: generate_body(some: 'response'))
|
28
|
-
end
|
29
|
-
|
30
|
-
specify do
|
31
|
-
expect(subject.request(:get, 'somepath/')).to eq('some' => 'response')
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
context 'called with supported method returning a a client error' do
|
36
|
-
before do
|
37
|
-
stub_request(:post, endpoint_uri('instances/')).
|
38
|
-
with(body: { 'name' => 'koza' },
|
39
|
-
headers: headers).
|
40
|
-
to_return(body: generate_body({name: ['This field can not be "koza"']}),
|
41
|
-
status: 400)
|
42
|
-
end
|
10
|
+
describe '#authenticate' do
|
11
|
+
subject { described_class.new email: email, password: password }
|
43
12
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
13
|
+
let(:authenticate_uri) { endpoint_uri('account/auth/') }
|
14
|
+
let(:email) { 'kiszka@koza.com' }
|
15
|
+
let(:password) { 'kiszonka' }
|
16
|
+
let(:success_status) { 200 }
|
17
|
+
let(:unauthorized_status) { 401 }
|
49
18
|
|
50
|
-
context '
|
19
|
+
context 'successful' do
|
51
20
|
before do
|
52
|
-
|
53
|
-
|
21
|
+
expect(subject).to receive(:request).
|
22
|
+
with(:post, described_class::AUTH_PATH, email: email, password: password).
|
23
|
+
and_return('account_key' => 'kEy')
|
54
24
|
end
|
55
25
|
|
56
26
|
specify do
|
57
|
-
expect { subject.
|
58
|
-
to raise_error(Syncano::ServerError)
|
27
|
+
expect { subject.authenticate }.to change { subject.authenticated? }
|
59
28
|
end
|
60
29
|
end
|
61
30
|
|
62
|
-
context '
|
31
|
+
context 'failed' do
|
63
32
|
before do
|
64
|
-
|
65
|
-
end
|
66
|
-
|
67
|
-
specify do
|
68
|
-
expect { subject.request(:get, endpoint_uri('weird/')) }.to raise_error(Syncano::UnsupportedStatusError)
|
69
|
-
end
|
70
|
-
end
|
33
|
+
expect(subject).to receive(:request).and_raise('auth failed')
|
71
34
|
|
72
|
-
context 'successful returning empty body' do
|
73
|
-
before do
|
74
|
-
stub_request(:delete, endpoint_uri('instances/kiszonka/')).
|
75
|
-
with(headers: headers).
|
76
|
-
to_return(body: nil, status: 204)
|
77
35
|
end
|
78
36
|
|
79
37
|
specify do
|
80
|
-
expect { subject.
|
81
|
-
to_not raise_error
|
38
|
+
expect { subject.authenticate }.to raise_error('auth failed')
|
82
39
|
end
|
83
|
-
|
84
40
|
end
|
85
41
|
end
|
86
42
|
|
87
|
-
describe '#
|
88
|
-
|
43
|
+
describe '#request' do
|
44
|
+
let(:headers) { { 'X-Api-Key'=>'87a7da987da98sd7a98', 'User-Agent' => "Syncano Ruby Gem #{Syncano::VERSION}" } }
|
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 }
|
89
49
|
|
90
|
-
|
91
|
-
let(:email) { 'kiszka@koza.com' }
|
92
|
-
let(:password) { 'kiszonka' }
|
93
|
-
let(:success_status) { 200 }
|
94
|
-
let(:unauthorized_status) { 401 }
|
50
|
+
subject { described_class.new(connection_params) }
|
95
51
|
|
96
|
-
context '
|
52
|
+
context 'with supported method' do
|
97
53
|
before do
|
98
|
-
stub_request(:
|
99
|
-
with(
|
100
|
-
to_return(body: successful_body, status: success_status)
|
101
|
-
end
|
54
|
+
stub_request(:get, endpoint_uri('user/method/')).
|
55
|
+
with(headers: headers).to_return(raw_response)
|
102
56
|
|
103
|
-
|
104
|
-
|
57
|
+
expect(Syncano::Response).
|
58
|
+
to receive(:handle) { |raw_response| expect(raw_response.body).to eq('koza') }.
|
59
|
+
and_return(handled_response)
|
105
60
|
end
|
106
61
|
|
107
|
-
|
108
|
-
|
109
|
-
email: email,
|
110
|
-
first_name: '',
|
111
|
-
last_name: '',
|
112
|
-
account_key: 'kozakoza123'
|
62
|
+
specify do
|
63
|
+
expect(subject.request(:get, 'user/method/')).to eq(handled_response)
|
113
64
|
end
|
114
65
|
end
|
115
66
|
|
116
|
-
context '
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
to_return(body: failed_body, status: unauthorized_status)
|
121
|
-
|
122
|
-
end
|
123
|
-
|
124
|
-
it 'should raise an exception' do
|
125
|
-
expect { subject.authenticate(email, password) }.to raise_error(Syncano::ClientError)
|
126
|
-
end
|
127
|
-
|
128
|
-
def failed_body
|
129
|
-
generate_body detail: 'Invalid email or password.'
|
67
|
+
context 'with unsupported method' do
|
68
|
+
specify do
|
69
|
+
expect { subject.request :koza, 'fafarafa' }.
|
70
|
+
to raise_error(RuntimeError, 'Unsupported method "koza"')
|
130
71
|
end
|
131
72
|
end
|
132
73
|
end
|
133
|
-
|
134
|
-
def generate_body(params)
|
135
|
-
JSON.generate params
|
136
|
-
end
|
137
74
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require_relative '../../spec_helper'
|
2
|
+
|
3
|
+
describe ::Syncano::Resources::Paths do
|
4
|
+
let(:koza) { double 'KozaClass' }
|
5
|
+
|
6
|
+
context 'collections' do
|
7
|
+
subject { described_class.instance.collections }
|
8
|
+
|
9
|
+
before { subject.define '/kozas/{koza_name}/kiszkas/', koza }
|
10
|
+
|
11
|
+
specify { expect(subject.match('/kozas/mykoza/kiszkas/')).to eq(koza) }
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'members' do
|
15
|
+
subject { described_class.instance.members }
|
16
|
+
|
17
|
+
before { subject.define '/kozas/{koza_name}/', koza }
|
18
|
+
|
19
|
+
xspecify { expect(subject.match('kozas/mykoza/')).to eq(koza) }
|
20
|
+
end
|
21
|
+
end
|
@@ -74,7 +74,7 @@ describe Syncano::Resources::Base do
|
|
74
74
|
{ 'type' => 'list',
|
75
75
|
'name' => 'webhooks' }] },
|
76
76
|
:collection => { :path => '/v1/instances/',
|
77
|
-
|
77
|
+
:http_methods => ['post',
|
78
78
|
'get'],
|
79
79
|
:params => [] },
|
80
80
|
:member => { :path => '/v1/instances/{ name }/',
|
@@ -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' })
|
144
144
|
expect(resource.name).to eq('test')
|
145
145
|
end
|
146
146
|
|
@@ -153,33 +153,94 @@ 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 }
|
156
163
|
|
157
|
-
|
158
|
-
resource = subject.new(connection, {}, { links: { self: '/v1/instances/test/' } }, true)
|
159
|
-
expect(resource.new_record?).to be(false)
|
164
|
+
specify { expect(resource.new_record?).to eq(true) }
|
160
165
|
end
|
161
166
|
|
162
|
-
|
163
|
-
|
164
|
-
|
167
|
+
context 'is false' do
|
168
|
+
let(:from_db) { true }
|
169
|
+
|
170
|
+
specify { expect(resource.new_record?).to eq(false) }
|
165
171
|
end
|
166
172
|
end
|
167
173
|
|
168
|
-
describe
|
169
|
-
|
174
|
+
describe "persisting data" do
|
175
|
+
shared_context "resource invalid" do
|
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 }
|
170
184
|
|
185
|
+
expect(connection).to receive(:request).
|
186
|
+
with(instance_of(Symbol), instance_of(String), instance_of(Hash)).
|
187
|
+
and_return({})
|
188
|
+
end
|
171
189
|
end
|
172
|
-
end
|
173
190
|
|
174
|
-
|
175
|
-
it 'should create update object\'s attributes in Syncano' do
|
191
|
+
let(:resource) { subject.new connection, {}, {}, false }
|
176
192
|
|
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) }
|
177
200
|
end
|
178
|
-
end
|
179
201
|
|
180
|
-
|
181
|
-
|
202
|
+
describe ".create" do
|
203
|
+
before do
|
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"
|
221
|
+
|
222
|
+
specify { expect(resource.save).to be_kind_of(Syncano::Resources::Base) }
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
describe "#save!" do
|
227
|
+
context "when invalid" do
|
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"
|
182
239
|
|
240
|
+
specify do
|
241
|
+
expect(resource.save!).to be_kind_of(Syncano::Resources::Base)
|
242
|
+
end
|
243
|
+
end
|
183
244
|
end
|
184
245
|
end
|
185
246
|
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Syncano::Response do
|
4
|
+
describe '.handle' do
|
5
|
+
context 'success' do
|
6
|
+
let(:raw_response) { double :raw_response,
|
7
|
+
body: generate_body(some: 'response'),
|
8
|
+
status: status(200) }
|
9
|
+
|
10
|
+
specify do
|
11
|
+
expect(described_class.handle(raw_response)).to eq('some' => 'response')
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'not found' do
|
16
|
+
let(:env) { double :env, url: 'http://path', method: :get }
|
17
|
+
let(:raw_response) { double :raw_response,
|
18
|
+
body: nil,
|
19
|
+
status: status(404),
|
20
|
+
env: env }
|
21
|
+
|
22
|
+
specify do
|
23
|
+
expect { described_class.handle(raw_response) }.to raise_error(Syncano::NotFound)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'client error' do
|
28
|
+
let(:raw_response) {
|
29
|
+
double :raw_response, body: generate_body({name: ['This field can not be "koza"']}),
|
30
|
+
status: status(400)
|
31
|
+
}
|
32
|
+
|
33
|
+
specify do
|
34
|
+
expect { described_class.handle(raw_response) }.to raise_error(Syncano::ClientError)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'returning a server error' do
|
39
|
+
let(:raw_response) { double :raw_response,
|
40
|
+
body: 'server error',
|
41
|
+
status: status(500) }
|
42
|
+
|
43
|
+
specify do
|
44
|
+
expect { described_class.handle(raw_response) }.
|
45
|
+
to raise_error(Syncano::ServerError)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'unsupported status code' do
|
50
|
+
let(:raw_response) { double :raw_response,
|
51
|
+
body: 'fafarafa',
|
52
|
+
status: status(112) }
|
53
|
+
|
54
|
+
specify do
|
55
|
+
expect { described_class.handle(raw_response) }.to raise_error(Syncano::UnsupportedStatusError)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'successful returning empty body' do
|
60
|
+
let(:raw_response) { double :raw_response, status: status(204) }
|
61
|
+
|
62
|
+
specify do
|
63
|
+
expect(described_class.handle(raw_response)).to eq(nil)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def generate_body(params)
|
69
|
+
JSON.generate params
|
70
|
+
end
|
71
|
+
|
72
|
+
def status(code)
|
73
|
+
double :status, code: code
|
74
|
+
end
|
75
|
+
end
|
@@ -22,4 +22,14 @@ describe Syncano::Schema::ResourceDefinition do
|
|
22
22
|
it 'should delete colliding links' do
|
23
23
|
expect(definition[:associations]['links']).to be_empty
|
24
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
|
34
|
+
end
|
25
35
|
end
|
data/spec/unit/schema_spec.rb
CHANGED
@@ -1,66 +1,16 @@
|
|
1
1
|
require_relative '../spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
require 'rspec/expectations'
|
5
|
-
require 'active_attr/matchers/have_attribute_matcher'
|
6
|
-
require 'shoulda-matchers'
|
7
|
-
|
8
3
|
describe Syncano::Schema do
|
9
|
-
|
10
|
-
|
11
|
-
let(:connection) { double("connection") }
|
12
|
-
|
13
|
-
subject { described_class.new connection }
|
4
|
+
let(:connection) { double 'connection' }
|
14
5
|
|
15
6
|
before do
|
16
|
-
expect(connection).to receive(:request).with(:get, described_class
|
17
|
-
|
18
|
-
Syncano::Resources.instance_eval do
|
19
|
-
constants.each do |const|
|
20
|
-
if ![:Base, :Collection, :Space].include?(const) && const_defined?(const)
|
21
|
-
remove_const const
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|
7
|
+
expect(connection).to receive(:request).with(:get, described_class.schema_path) { schema }
|
25
8
|
end
|
26
9
|
|
27
|
-
|
28
|
-
it 'defintes classes according to the schema' do
|
29
|
-
expect { Syncano::Resources::Class }.to raise_error(NameError)
|
30
|
-
|
31
|
-
subject.process!
|
32
|
-
|
33
|
-
expect { Syncano::Resources::Class }.to_not raise_error
|
34
|
-
|
35
|
-
expect(Syncano::Resources::Class).to have_attribute(:name)
|
36
|
-
expect(Syncano::Resources::Class).to have_attribute(:status)
|
37
|
-
expect(Syncano::Resources::Class).to have_attribute(:created_at)
|
38
|
-
expect(Syncano::Resources::Class).to have_attribute(:description)
|
39
|
-
expect(Syncano::Resources::Class).to have_attribute(:updated_at)
|
40
|
-
expect(Syncano::Resources::Class).to have_attribute(:objects_count)
|
41
|
-
expect(Syncano::Resources::Class).to have_attribute(:metadata)
|
42
|
-
expect(Syncano::Resources::Class).to have_attribute(:revision)
|
43
|
-
|
44
|
-
class_instance = Syncano::Resources::Class.new(connection, {}, { links: {} })
|
45
|
-
|
46
|
-
expect(class_instance).to validate_presence_of(:name)
|
47
|
-
expect(class_instance).to validate_length_of(:name).is_at_most(50)
|
48
|
-
|
49
|
-
expect(class_instance).to respond_to(:objects)
|
50
|
-
|
51
|
-
code_box_instance = Syncano::Resources::CodeBox.new(connection, {}, { links: {} })
|
52
|
-
expect(code_box_instance).to validate_inclusion_of(:runtime_name).
|
53
|
-
in_array(%w(nodejs ruby python))
|
54
|
-
|
55
|
-
end
|
56
|
-
|
57
|
-
it 'defines foreign keys attributes when attributes names collide with links' do
|
58
|
-
subject.process!
|
59
|
-
|
60
|
-
schedule_instance = Syncano::Resources::Schedule.new connection, {}, links: {}
|
10
|
+
subject { described_class.new(connection) }
|
61
11
|
|
62
|
-
|
63
|
-
|
12
|
+
specify '#resource_definitions' do
|
13
|
+
expect(subject.definition).to be_kind_of(Hash)
|
64
14
|
end
|
65
15
|
|
66
16
|
def schema
|