syncano 3.1.4 → 4.0.0.alpha
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 +7 -0
- data/.gitignore +4 -1
- data/.rspec +2 -0
- data/.rubocop.yml +3 -0
- data/.ruby-version +1 -0
- data/Gemfile +25 -1
- data/Guardfile +22 -4
- data/README.md +68 -447
- data/Rakefile +48 -5
- data/circle.yml +10 -0
- data/lib/active_attr/dirty.rb +3 -17
- data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
- data/lib/active_attr/typecasting_override.rb +29 -0
- data/lib/syncano.rb +53 -92
- data/lib/syncano/api.rb +13 -0
- data/lib/syncano/connection.rb +97 -0
- data/lib/syncano/model/associations.rb +121 -0
- data/lib/syncano/{active_record/association → model/associations}/base.rb +5 -5
- data/lib/syncano/{active_record/association → model/associations}/belongs_to.rb +6 -6
- data/lib/syncano/{active_record/association → model/associations}/has_many.rb +15 -9
- data/lib/syncano/{active_record/association → model/associations}/has_one.rb +4 -4
- data/lib/syncano/model/base.rb +257 -0
- data/lib/syncano/{active_record → model}/callbacks.rb +16 -13
- data/lib/syncano/{active_record → model}/scope_builder.rb +53 -69
- data/lib/syncano/query_builder.rb +19 -129
- data/lib/syncano/resources.rb +126 -0
- data/lib/syncano/resources/base.rb +304 -300
- data/lib/syncano/resources/collection.rb +19 -223
- data/lib/syncano/resources/space.rb +29 -0
- data/lib/syncano/schema.rb +86 -0
- data/lib/syncano/schema/attribute_definition.rb +83 -0
- data/lib/syncano/schema/resource_definition.rb +36 -0
- data/lib/syncano/scope.rb +10 -0
- data/lib/syncano/version.rb +3 -4
- data/spec/integration/syncano_spec.rb +228 -0
- data/spec/spec_helper.rb +15 -9
- data/spec/unit/api_spec.rb +5 -0
- data/spec/unit/connection_spec.rb +137 -0
- data/spec/unit/query_builder_spec.rb +75 -0
- data/spec/unit/resources/collection_spec.rb +36 -0
- data/spec/unit/resources/space_spec.rb +28 -0
- data/spec/unit/resources_base_spec.rb +185 -0
- data/spec/unit/schema/attribute_definition_spec.rb +18 -0
- data/spec/unit/schema/resource_definition_spec.rb +25 -0
- data/spec/unit/schema_spec.rb +3532 -0
- data/spec/unit/syncano_spec.rb +63 -0
- data/syncano.gemspec +8 -14
- metadata +85 -210
- data/lib/generators/syncano/install_generator.rb +0 -17
- data/lib/generators/syncano/templates/initializers/syncano.rb +0 -7
- data/lib/syncano/active_record/associations.rb +0 -112
- data/lib/syncano/active_record/base.rb +0 -318
- data/lib/syncano/batch_queue.rb +0 -58
- data/lib/syncano/batch_queue_element.rb +0 -33
- data/lib/syncano/clients/base.rb +0 -123
- data/lib/syncano/clients/rest.rb +0 -79
- data/lib/syncano/clients/sync.rb +0 -164
- data/lib/syncano/errors.rb +0 -17
- data/lib/syncano/jimson_client.rb +0 -66
- data/lib/syncano/packets/auth.rb +0 -27
- data/lib/syncano/packets/base.rb +0 -70
- data/lib/syncano/packets/call.rb +0 -34
- data/lib/syncano/packets/call_response.rb +0 -33
- data/lib/syncano/packets/error.rb +0 -19
- data/lib/syncano/packets/message.rb +0 -30
- data/lib/syncano/packets/notification.rb +0 -39
- data/lib/syncano/packets/ping.rb +0 -12
- data/lib/syncano/resources/admin.rb +0 -26
- data/lib/syncano/resources/api_key.rb +0 -108
- data/lib/syncano/resources/data_object.rb +0 -316
- data/lib/syncano/resources/folder.rb +0 -88
- data/lib/syncano/resources/notifications/base.rb +0 -103
- data/lib/syncano/resources/notifications/create.rb +0 -20
- data/lib/syncano/resources/notifications/destroy.rb +0 -20
- data/lib/syncano/resources/notifications/message.rb +0 -9
- data/lib/syncano/resources/notifications/update.rb +0 -24
- data/lib/syncano/resources/project.rb +0 -96
- data/lib/syncano/resources/role.rb +0 -11
- data/lib/syncano/resources/subscription.rb +0 -12
- data/lib/syncano/resources/user.rb +0 -65
- data/lib/syncano/response.rb +0 -22
- data/lib/syncano/sync_connection.rb +0 -133
- data/spec/admins_spec.rb +0 -16
- data/spec/api_keys_spec.rb +0 -34
- data/spec/collections_spec.rb +0 -67
- data/spec/data_objects_spec.rb +0 -113
- data/spec/folders_spec.rb +0 -39
- data/spec/notifications_spec.rb +0 -43
- data/spec/projects_spec.rb +0 -35
- data/spec/roles_spec.rb +0 -13
- data/spec/sync_resources_spec.rb +0 -35
- data/spec/syncano_spec.rb +0 -9
@@ -0,0 +1,36 @@
|
|
1
|
+
module Syncano
|
2
|
+
class Schema
|
3
|
+
class ResourceDefinition
|
4
|
+
attr_accessor :attributes
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
def initialize(name, raw_defitnition)
|
8
|
+
@raw_definition = raw_defitnition
|
9
|
+
|
10
|
+
delete_colliding_links
|
11
|
+
|
12
|
+
self.name = name
|
13
|
+
|
14
|
+
self.attributes = raw_defitnition[:attributes].map do |name, raw_attribute_definition|
|
15
|
+
Syncano::Schema::AttributeDefinition.new name, raw_attribute_definition
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def [](key)
|
20
|
+
@raw_definition[key]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def delete_colliding_links
|
26
|
+
@raw_definition[:attributes].each do |k, v|
|
27
|
+
if @raw_definition[:associations]['links']
|
28
|
+
@raw_definition[:associations]['links'].delete_if do |link|
|
29
|
+
link['name'] == k
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/syncano/version.rb
CHANGED
@@ -1,4 +1,3 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
end
|
1
|
+
module Syncano
|
2
|
+
VERSION = '4.0.0.alpha'
|
3
|
+
end
|
@@ -0,0 +1,228 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
|
3
|
+
WebMock.allow_net_connect!
|
4
|
+
|
5
|
+
describe Syncano do
|
6
|
+
before(:all) do
|
7
|
+
@api_key = ENV['INTEGRATION_TEST_API_KEY']
|
8
|
+
@api = Syncano.connect(api_key: @api_key)
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@api.instances.all.each &:destroy
|
13
|
+
@instance = @api.instances.create(name: "a#{SecureRandom.hex(24)}")
|
14
|
+
@instance.classes.all.select { |c| c.name != 'user_profile'}.each &:destroy
|
15
|
+
@instance.groups.all.each &:destroy
|
16
|
+
@instance.users.all.each &:delete
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'working with instances' do
|
20
|
+
subject { @api.instances }
|
21
|
+
|
22
|
+
it 'should raise an error on not found instance' do
|
23
|
+
expect { subject.find('kaszanka') }.to raise_error(Syncano::ClientError)
|
24
|
+
end
|
25
|
+
|
26
|
+
specify do
|
27
|
+
subject.create name: 'fafarafaa'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'working with classes' do
|
32
|
+
subject { @instance.classes }
|
33
|
+
|
34
|
+
specify do
|
35
|
+
expect { subject.create name: 'sausage', schema: [{name: 'name', type: 'string' }] }.to create_resource
|
36
|
+
|
37
|
+
new_klass = subject.last
|
38
|
+
|
39
|
+
expect(new_klass.name).to eq('sausage')
|
40
|
+
expect(new_klass.schema).to eq([{'name' => 'name', 'type' => 'string'}])
|
41
|
+
|
42
|
+
new_klass.description = 'salchichón'
|
43
|
+
new_klass.schema = [{name: 'nombre', type: 'string'}]
|
44
|
+
|
45
|
+
saved_class = new_klass.save
|
46
|
+
expect(resources_count).to eq(2)
|
47
|
+
expect(saved_class.schema).to eq([{'name' => 'nombre', 'type' => 'string'}])
|
48
|
+
expect(saved_class.description).to eq('salchichón')
|
49
|
+
|
50
|
+
from_database = subject.last
|
51
|
+
expect(from_database.schema).to eq([{'name' => 'nombre', 'type' => 'string'}])
|
52
|
+
expect(from_database.description).to eq('salchichón')
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe 'working with objects' do
|
57
|
+
before do
|
58
|
+
@class = @instance.classes.create name: 'account',
|
59
|
+
schema: [{name: 'currency', type: 'string', filter_index: true},
|
60
|
+
{name: 'ballance', type: 'integer', filter_index: true, order_index: true}]
|
61
|
+
end
|
62
|
+
|
63
|
+
subject { @class.objects }
|
64
|
+
|
65
|
+
|
66
|
+
specify 'basic operations' do
|
67
|
+
expect { subject.create currency: 'USD', ballance: 1337 }.to create_resource
|
68
|
+
|
69
|
+
object = subject.first
|
70
|
+
|
71
|
+
expect(object.ballance).to eq(1337)
|
72
|
+
expect(object.currency).to eq('USD')
|
73
|
+
|
74
|
+
object.currency = 'GBP'
|
75
|
+
object.ballance = 54
|
76
|
+
object.save
|
77
|
+
|
78
|
+
expect(object.ballance).to eq(54)
|
79
|
+
expect(object.currency).to eq('GBP')
|
80
|
+
|
81
|
+
expect { object.destroy }.to destroy_resource
|
82
|
+
end
|
83
|
+
|
84
|
+
|
85
|
+
specify 'PATH and POST' do
|
86
|
+
initial_yuan = subject.create currency: 'CNY', ballance: 98123
|
87
|
+
|
88
|
+
yuan = subject.first
|
89
|
+
new_yuan = subject.first
|
90
|
+
|
91
|
+
yuan.ballance = 100000
|
92
|
+
yuan.save
|
93
|
+
|
94
|
+
new_yuan.currency = 'RMB'
|
95
|
+
new_yuan.save
|
96
|
+
|
97
|
+
yuan = subject.first
|
98
|
+
|
99
|
+
expect(yuan.currency).to eq('RMB')
|
100
|
+
expect(yuan.ballance).to eq(100000)
|
101
|
+
|
102
|
+
initial_yuan.save(overwrite: true)
|
103
|
+
yuan.reload!
|
104
|
+
|
105
|
+
expect(yuan.currency).to eq('CNY')
|
106
|
+
expect(yuan.ballance).to eq(98123)
|
107
|
+
end
|
108
|
+
|
109
|
+
specify 'filtering and ordering' do
|
110
|
+
usd = subject.create(currency: 'USD', ballance: 400)
|
111
|
+
pln = subject.create(currency: 'PLN', ballance: 1600)
|
112
|
+
eur = subject.create(currency: 'EUR', ballance: 400)
|
113
|
+
gbp = subject.create(currency: 'GPB', ballance: 270)
|
114
|
+
chf = subject.create(currency: 'CHF', ballance: 390)
|
115
|
+
uah = subject.create(currency: 'UAH', ballance: 9100)
|
116
|
+
rub = subject.create(currency: 'RUB')
|
117
|
+
|
118
|
+
expect(subject.all(query: { ballance: { _exists: true }}).to_a).to_not include(rub)
|
119
|
+
expect(subject.all(query: { currency: { _in: %w[UAH USD PLN] } }).to_a).to match_array([pln, usd, uah])
|
120
|
+
expect(subject.all(query: { ballance: { _lt: 400, _gte: 270 }}, order_by: '-ballance').to_a).to eq([chf, gbp])
|
121
|
+
end
|
122
|
+
|
123
|
+
specify 'fetching only specific fields' do
|
124
|
+
subject.create(currency: 'USD', ballance: 400)
|
125
|
+
|
126
|
+
account = subject.all(fields: 'currency').first
|
127
|
+
expect { account.currency }.to_not raise_error
|
128
|
+
expect { account.ballance }.to raise_error(NoMethodError)
|
129
|
+
|
130
|
+
|
131
|
+
account = subject.first(excluded_fields: 'currency')
|
132
|
+
expect { account.currency }.to raise_error(NoMethodError)
|
133
|
+
expect { account.ballance }.to_not raise_error
|
134
|
+
end
|
135
|
+
|
136
|
+
specify 'paging', slow: true do
|
137
|
+
104.times { subject.create }
|
138
|
+
|
139
|
+
total = 0
|
140
|
+
all = subject.all
|
141
|
+
|
142
|
+
loop do
|
143
|
+
total += all.count
|
144
|
+
|
145
|
+
if all.next?
|
146
|
+
all = subject.space(all.last).all
|
147
|
+
else
|
148
|
+
break
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
expect(total).to eq(104)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
describe 'working with codeboxes traces' do
|
157
|
+
subject { @instance.codeboxes }
|
158
|
+
|
159
|
+
|
160
|
+
specify 'basic operations' do
|
161
|
+
expect { subject.create name: 'df', source: 'puts 1337', runtime_name: 'ruby' }.to create_resource
|
162
|
+
|
163
|
+
codebox = subject.first
|
164
|
+
codebox.run
|
165
|
+
codebox.source = 'puts 123'
|
166
|
+
codebox.save
|
167
|
+
codebox.run
|
168
|
+
|
169
|
+
without_profiling { sleep 5 }
|
170
|
+
traces = codebox.traces.all
|
171
|
+
|
172
|
+
expect(traces.count).to eq(2)
|
173
|
+
|
174
|
+
first = traces[1]
|
175
|
+
|
176
|
+
expect(first.status).to eq('success')
|
177
|
+
expect(first.result).to eq('1337')
|
178
|
+
|
179
|
+
second = traces[0]
|
180
|
+
expect(second.status).to eq('success')
|
181
|
+
expect(second.result).to eq('123')
|
182
|
+
|
183
|
+
expect { @instance.schedules.create name: 'test', interval_sec: 30, codebox: codebox.primary_key }.
|
184
|
+
to change { @instance.schedules.all.count }.by(1)
|
185
|
+
|
186
|
+
expect { codebox.destroy }.to destroy_resource
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe 'working with webhooks' do
|
191
|
+
subject { @instance.webhooks }
|
192
|
+
|
193
|
+
let!(:codebox) { @instance.codeboxes.create name: 'wurst', source: 'puts "currywurst"', runtime_name: 'ruby' }
|
194
|
+
|
195
|
+
specify do
|
196
|
+
expect { subject.create slug: 'web-wurst', codebox: codebox.primary_key }.to create_resource
|
197
|
+
|
198
|
+
expect(subject.first.run['result']).to eq('currywurst')
|
199
|
+
|
200
|
+
expect { subject.first.destroy }.to destroy_resource
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def resources_count
|
205
|
+
subject.all.count
|
206
|
+
end
|
207
|
+
|
208
|
+
def create_resource
|
209
|
+
change { resources_count }.by(1)
|
210
|
+
end
|
211
|
+
|
212
|
+
def destroy_resource
|
213
|
+
change { resources_count }.to(0)
|
214
|
+
end
|
215
|
+
|
216
|
+
def without_profiling
|
217
|
+
if defined? RubyProf
|
218
|
+
begin
|
219
|
+
RubyProf.pause if RubyProf.running?
|
220
|
+
yield
|
221
|
+
ensure
|
222
|
+
RubyProf.resume if RubyProf.running?
|
223
|
+
end
|
224
|
+
else
|
225
|
+
yield
|
226
|
+
end
|
227
|
+
end
|
228
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,13 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler.setup
|
3
|
+
|
4
|
+
require 'dotenv'
|
5
|
+
Dotenv.load
|
6
|
+
|
7
|
+
require 'rspec-prof' if ENV['SPEC_PROFILE']
|
1
8
|
require 'syncano'
|
2
|
-
require '
|
9
|
+
require 'webmock/rspec'
|
10
|
+
|
11
|
+
WebMock.disable_net_connect!
|
3
12
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
@syncano_api_key = ENV['SYNCANO_API_KEY'] || raise('Set SYNCANO_API_KEY environment variable!')
|
8
|
-
@syncano_instance_name = ENV['SYNCANO_INSTANCE_NAME'] || raise('Set SYNCANO_INSTANCE_NAME environment variable!')
|
13
|
+
def generate_body(params)
|
14
|
+
JSON.generate(params)
|
15
|
+
end
|
9
16
|
|
10
|
-
|
11
|
-
|
12
|
-
end
|
17
|
+
def endpoint_uri(path)
|
18
|
+
[Syncano::Connection.api_root,"v1", path].join("/")
|
13
19
|
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require_relative '../spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
describe Syncano::Connection do
|
5
|
+
context 'api_key' do
|
6
|
+
specify { expect(described_class.new(api_key: 'fafarafa')).to be_authenticated }
|
7
|
+
specify { expect(described_class.new).to_not be_authenticated }
|
8
|
+
end
|
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
|
43
|
+
|
44
|
+
specify do
|
45
|
+
expect { subject.request(:post, '/v1/instances/', { name: "koza" }) }.
|
46
|
+
to raise_error(Syncano::ClientError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'returning a server error' do
|
51
|
+
before do
|
52
|
+
stub_request(:get, endpoint_uri('error_prone/')).
|
53
|
+
to_return(body: 'An error occured', status: 500)
|
54
|
+
end
|
55
|
+
|
56
|
+
specify do
|
57
|
+
expect { subject.request(:get, endpoint_uri('error_prone/'), nil) }.
|
58
|
+
to raise_error(Syncano::ServerError)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'returning unsupported status code' do
|
63
|
+
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
|
71
|
+
|
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
|
+
end
|
78
|
+
|
79
|
+
specify do
|
80
|
+
expect { subject.request(:delete, '/v1/instances/kiszonka/', {}) }.
|
81
|
+
to_not raise_error
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#authenticate' do
|
88
|
+
subject { described_class.new }
|
89
|
+
|
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 }
|
95
|
+
|
96
|
+
context 'successful' do
|
97
|
+
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
|
102
|
+
|
103
|
+
it 'should get an API key' do
|
104
|
+
expect { subject.authenticate(email, password) }.to change { subject.authenticated? }
|
105
|
+
end
|
106
|
+
|
107
|
+
def successful_body
|
108
|
+
generate_body id: 15,
|
109
|
+
email: email,
|
110
|
+
first_name: '',
|
111
|
+
last_name: '',
|
112
|
+
account_key: 'kozakoza123'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
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.'
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_body(params)
|
135
|
+
JSON.generate params
|
136
|
+
end
|
137
|
+
end
|