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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -157
  3. data/circle.yml +1 -1
  4. data/lib/syncano.rb +38 -11
  5. data/lib/syncano/api.rb +20 -2
  6. data/lib/syncano/api/endpoints.rb +17 -0
  7. data/lib/syncano/connection.rb +46 -54
  8. data/lib/syncano/poller.rb +55 -0
  9. data/lib/syncano/resources.rb +48 -16
  10. data/lib/syncano/resources/base.rb +46 -56
  11. data/lib/syncano/resources/paths.rb +48 -0
  12. data/lib/syncano/resources/resource_invalid.rb +15 -0
  13. data/lib/syncano/response.rb +55 -0
  14. data/lib/syncano/schema.rb +10 -29
  15. data/lib/syncano/schema/attribute_definition.rb +2 -2
  16. data/lib/syncano/schema/endpoints_whitelist.rb +40 -0
  17. data/lib/syncano/schema/resource_definition.rb +5 -0
  18. data/lib/syncano/upload_io.rb +7 -0
  19. data/lib/syncano/version.rb +1 -1
  20. data/spec/integration/syncano_spec.rb +220 -15
  21. data/spec/spec_helper.rb +3 -1
  22. data/spec/unit/connection_spec.rb +34 -97
  23. data/spec/unit/resources/paths_spec.rb +21 -0
  24. data/spec/unit/resources_base_spec.rb +77 -16
  25. data/spec/unit/response_spec.rb +75 -0
  26. data/spec/unit/schema/resource_definition_spec.rb +10 -0
  27. data/spec/unit/schema_spec.rb +5 -55
  28. data/syncano.gemspec +4 -0
  29. metadata +69 -13
  30. data/lib/active_attr/dirty.rb +0 -26
  31. data/lib/active_attr/typecasting/hash_typecaster.rb +0 -34
  32. data/lib/active_attr/typecasting_override.rb +0 -29
  33. data/lib/syncano/model/associations.rb +0 -121
  34. data/lib/syncano/model/associations/base.rb +0 -38
  35. data/lib/syncano/model/associations/belongs_to.rb +0 -30
  36. data/lib/syncano/model/associations/has_many.rb +0 -75
  37. data/lib/syncano/model/associations/has_one.rb +0 -22
  38. data/lib/syncano/model/base.rb +0 -257
  39. data/lib/syncano/model/callbacks.rb +0 -49
  40. 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 '#request' do
11
- let(:headers) { { 'X-Api-Key'=>'87a7da987da98sd7a98', 'User-Agent' => "Syncano Ruby Gem #{Syncano::VERSION}" } }
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
- specify do
45
- expect { subject.request(:post, '/v1/instances/', { name: "koza" }) }.
46
- to raise_error(Syncano::ClientError)
47
- end
48
- end
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 'returning a server error' do
19
+ context 'successful' do
51
20
  before do
52
- stub_request(:get, endpoint_uri('error_prone/')).
53
- to_return(body: 'An error occured', status: 500)
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.request(:get, endpoint_uri('error_prone/'), nil) }.
58
- to raise_error(Syncano::ServerError)
27
+ expect { subject.authenticate }.to change { subject.authenticated? }
59
28
  end
60
29
  end
61
30
 
62
- context 'returning unsupported status code' do
31
+ context 'failed' do
63
32
  before do
64
- stub_request(:get, endpoint_uri('weird/')).to_return(body: 'O HAI!', status: 101)
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.request(:delete, '/v1/instances/kiszonka/', {}) }.
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 '#authenticate' do
88
- subject { described_class.new }
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
- let(:authenticate_uri) { endpoint_uri('account/auth/') }
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 'successful' do
52
+ context 'with supported method' do
97
53
  before do
98
- stub_request(:post, authenticate_uri).
99
- with(body: { 'email' => email, 'password' => password } ).
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
- it 'should get an API key' do
104
- expect { subject.authenticate(email, password) }.to change { subject.authenticated? }
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
- def successful_body
108
- generate_body id: 15,
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 'failed' do
117
- before do
118
- stub_request(:post, authenticate_uri).
119
- with(body: { 'email' => email, 'password' => password }).
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
- :http_methods => ['post',
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' }, true)
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
- 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)
164
+ specify { expect(resource.new_record?).to eq(true) }
160
165
  end
161
166
 
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)
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 '.create' do
169
- it 'should create new object in Syncano' do
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
- describe '.update_attributes' do
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
- describe '.destroy' do
181
- it 'should delete object from Syncano' do
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
@@ -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
- include ActiveAttr::Matchers
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::SCHEMA_PATH) { schema }
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
- describe 'process!' do
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
- expect(schedule_instance).to respond_to(:codebox)
63
- end
12
+ specify '#resource_definitions' do
13
+ expect(subject.definition).to be_kind_of(Hash)
64
14
  end
65
15
 
66
16
  def schema