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.
Files changed (92) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -1
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +3 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +25 -1
  7. data/Guardfile +22 -4
  8. data/README.md +68 -447
  9. data/Rakefile +48 -5
  10. data/circle.yml +10 -0
  11. data/lib/active_attr/dirty.rb +3 -17
  12. data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
  13. data/lib/active_attr/typecasting_override.rb +29 -0
  14. data/lib/syncano.rb +53 -92
  15. data/lib/syncano/api.rb +13 -0
  16. data/lib/syncano/connection.rb +97 -0
  17. data/lib/syncano/model/associations.rb +121 -0
  18. data/lib/syncano/{active_record/association → model/associations}/base.rb +5 -5
  19. data/lib/syncano/{active_record/association → model/associations}/belongs_to.rb +6 -6
  20. data/lib/syncano/{active_record/association → model/associations}/has_many.rb +15 -9
  21. data/lib/syncano/{active_record/association → model/associations}/has_one.rb +4 -4
  22. data/lib/syncano/model/base.rb +257 -0
  23. data/lib/syncano/{active_record → model}/callbacks.rb +16 -13
  24. data/lib/syncano/{active_record → model}/scope_builder.rb +53 -69
  25. data/lib/syncano/query_builder.rb +19 -129
  26. data/lib/syncano/resources.rb +126 -0
  27. data/lib/syncano/resources/base.rb +304 -300
  28. data/lib/syncano/resources/collection.rb +19 -223
  29. data/lib/syncano/resources/space.rb +29 -0
  30. data/lib/syncano/schema.rb +86 -0
  31. data/lib/syncano/schema/attribute_definition.rb +83 -0
  32. data/lib/syncano/schema/resource_definition.rb +36 -0
  33. data/lib/syncano/scope.rb +10 -0
  34. data/lib/syncano/version.rb +3 -4
  35. data/spec/integration/syncano_spec.rb +228 -0
  36. data/spec/spec_helper.rb +15 -9
  37. data/spec/unit/api_spec.rb +5 -0
  38. data/spec/unit/connection_spec.rb +137 -0
  39. data/spec/unit/query_builder_spec.rb +75 -0
  40. data/spec/unit/resources/collection_spec.rb +36 -0
  41. data/spec/unit/resources/space_spec.rb +28 -0
  42. data/spec/unit/resources_base_spec.rb +185 -0
  43. data/spec/unit/schema/attribute_definition_spec.rb +18 -0
  44. data/spec/unit/schema/resource_definition_spec.rb +25 -0
  45. data/spec/unit/schema_spec.rb +3532 -0
  46. data/spec/unit/syncano_spec.rb +63 -0
  47. data/syncano.gemspec +8 -14
  48. metadata +85 -210
  49. data/lib/generators/syncano/install_generator.rb +0 -17
  50. data/lib/generators/syncano/templates/initializers/syncano.rb +0 -7
  51. data/lib/syncano/active_record/associations.rb +0 -112
  52. data/lib/syncano/active_record/base.rb +0 -318
  53. data/lib/syncano/batch_queue.rb +0 -58
  54. data/lib/syncano/batch_queue_element.rb +0 -33
  55. data/lib/syncano/clients/base.rb +0 -123
  56. data/lib/syncano/clients/rest.rb +0 -79
  57. data/lib/syncano/clients/sync.rb +0 -164
  58. data/lib/syncano/errors.rb +0 -17
  59. data/lib/syncano/jimson_client.rb +0 -66
  60. data/lib/syncano/packets/auth.rb +0 -27
  61. data/lib/syncano/packets/base.rb +0 -70
  62. data/lib/syncano/packets/call.rb +0 -34
  63. data/lib/syncano/packets/call_response.rb +0 -33
  64. data/lib/syncano/packets/error.rb +0 -19
  65. data/lib/syncano/packets/message.rb +0 -30
  66. data/lib/syncano/packets/notification.rb +0 -39
  67. data/lib/syncano/packets/ping.rb +0 -12
  68. data/lib/syncano/resources/admin.rb +0 -26
  69. data/lib/syncano/resources/api_key.rb +0 -108
  70. data/lib/syncano/resources/data_object.rb +0 -316
  71. data/lib/syncano/resources/folder.rb +0 -88
  72. data/lib/syncano/resources/notifications/base.rb +0 -103
  73. data/lib/syncano/resources/notifications/create.rb +0 -20
  74. data/lib/syncano/resources/notifications/destroy.rb +0 -20
  75. data/lib/syncano/resources/notifications/message.rb +0 -9
  76. data/lib/syncano/resources/notifications/update.rb +0 -24
  77. data/lib/syncano/resources/project.rb +0 -96
  78. data/lib/syncano/resources/role.rb +0 -11
  79. data/lib/syncano/resources/subscription.rb +0 -12
  80. data/lib/syncano/resources/user.rb +0 -65
  81. data/lib/syncano/response.rb +0 -22
  82. data/lib/syncano/sync_connection.rb +0 -133
  83. data/spec/admins_spec.rb +0 -16
  84. data/spec/api_keys_spec.rb +0 -34
  85. data/spec/collections_spec.rb +0 -67
  86. data/spec/data_objects_spec.rb +0 -113
  87. data/spec/folders_spec.rb +0 -39
  88. data/spec/notifications_spec.rb +0 -43
  89. data/spec/projects_spec.rb +0 -35
  90. data/spec/roles_spec.rb +0 -13
  91. data/spec/sync_resources_spec.rb +0 -35
  92. 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
@@ -0,0 +1,10 @@
1
+ module Syncano
2
+ class Scope
3
+ attr_accessor :connection, :scope_parameters
4
+
5
+ def initialize(connection, scope_parameters)
6
+ self.connection = connection
7
+ self.scope_parameters = scope_parameters
8
+ end
9
+ end
10
+ end
@@ -1,4 +1,3 @@
1
- class Syncano
2
- # Syncano version number
3
- VERSION = '3.1.4'
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 'mocha/api'
9
+ require 'webmock/rspec'
10
+
11
+ WebMock.disable_net_connect!
3
12
 
4
- RSpec.configure do |config|
5
- config.mock_framework = :mocha
6
- config.before(:all) do
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
- @client = ::Syncano.client(instance_name: @syncano_instance_name, api_key: @syncano_api_key)
11
- @sync_client = ::Syncano.sync_client(instance_name: @syncano_instance_name, api_key: @syncano_api_key)
12
- end
17
+ def endpoint_uri(path)
18
+ [Syncano::Connection.api_root,"v1", path].join("/")
13
19
  end
@@ -0,0 +1,5 @@
1
+ require 'syncano'
2
+
3
+ describe Syncano::API do
4
+ let(:connection) { double }
5
+ 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