syncano 4.0.0.alpha1 → 4.0.0.alpha2
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/lib/syncano/schema.rb
CHANGED
@@ -1,35 +1,26 @@
|
|
1
1
|
require_relative './schema/attribute_definition'
|
2
2
|
require_relative './schema/resource_definition'
|
3
|
+
require_relative './schema/endpoints_whitelist'
|
4
|
+
|
5
|
+
require 'singleton'
|
3
6
|
|
4
7
|
module Syncano
|
5
8
|
class Schema
|
6
|
-
SCHEMA_PATH = 'schema/'
|
7
9
|
|
8
10
|
attr_reader :schema
|
9
11
|
|
10
|
-
def
|
11
|
-
|
12
|
-
load_schema
|
12
|
+
def self.schema_path
|
13
|
+
"/#{Syncano::Connection::API_VERSION}/schema/"
|
13
14
|
end
|
14
15
|
|
15
|
-
def
|
16
|
-
|
17
|
-
resource_definition = Syncano::Schema::ResourceDefinition.new(name, raw_resource_definition)
|
18
|
-
resource_class = ::Syncano::Resources.define_resource_class(resource_definition)
|
19
|
-
|
20
|
-
if resource_definition[:collection].present? && resource_definition[:collection][:path].scan(/\{([^}]+)\}/).empty?
|
21
|
-
self.class.generate_client_method(name, resource_class)
|
22
|
-
end
|
23
|
-
end
|
16
|
+
def initialize(connection = ::Syncano::Connection.new)
|
17
|
+
self.connection = connection
|
24
18
|
end
|
25
19
|
|
26
|
-
private
|
27
|
-
|
28
20
|
attr_accessor :connection
|
29
|
-
attr_writer :schema
|
30
21
|
|
31
|
-
def
|
32
|
-
raw_schema = connection.request(:get,
|
22
|
+
def definition
|
23
|
+
raw_schema = connection.request(:get, self.class.schema_path)
|
33
24
|
resources = {}
|
34
25
|
|
35
26
|
raw_schema.each do |resource_schema|
|
@@ -70,17 +61,7 @@ module Syncano
|
|
70
61
|
end
|
71
62
|
end
|
72
63
|
|
73
|
-
|
74
|
-
end
|
75
|
-
|
76
|
-
class << self
|
77
|
-
def generate_client_method(resource_name, resource_class)
|
78
|
-
method_name = resource_name.tableize
|
79
|
-
|
80
|
-
::Syncano::API.send(:define_method, method_name) do
|
81
|
-
::Syncano::QueryBuilder.new(connection, resource_class)
|
82
|
-
end
|
83
|
-
end
|
64
|
+
resources
|
84
65
|
end
|
85
66
|
end
|
86
67
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Syncano
|
2
|
+
class Schema
|
3
|
+
class EndpointsWhitelist
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
class SupportedDefinitionPredicate
|
7
|
+
attr_accessor :definition
|
8
|
+
|
9
|
+
def initialize(definition)
|
10
|
+
self.definition = definition
|
11
|
+
end
|
12
|
+
|
13
|
+
def call
|
14
|
+
path =~ /\A\/v1\/instances/ && path !~ /invitation/
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def path
|
20
|
+
definition[:collection] && definition[:collection][:path] ||
|
21
|
+
definition[:member] && definition[:member][:path]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
SUPPORTED_DEFINITIONS = -> (definition) {
|
26
|
+
SupportedDefinitionPredicate.new(definition).call
|
27
|
+
}
|
28
|
+
|
29
|
+
def initialize(schema)
|
30
|
+
@definition = schema.definition
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
@definition.select { |_name, definition|
|
35
|
+
SUPPORTED_DEFINITIONS === definition
|
36
|
+
}.each &block
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/syncano/version.rb
CHANGED
@@ -4,23 +4,28 @@ WebMock.allow_net_connect!
|
|
4
4
|
|
5
5
|
describe Syncano do
|
6
6
|
before(:all) do
|
7
|
+
Syncano::API.send :initialized=, false
|
8
|
+
|
7
9
|
@api_key = ENV['INTEGRATION_TEST_API_KEY']
|
8
10
|
@api = Syncano.connect(api_key: @api_key)
|
9
11
|
end
|
10
12
|
|
11
13
|
before(:each) do
|
12
14
|
@api.instances.all.each &:destroy
|
13
|
-
@instance = @api.instances.create(name:
|
15
|
+
@instance = @api.instances.create(name: instance_name )
|
14
16
|
@instance.classes.all.select { |c| c.name != 'user_profile'}.each &:destroy
|
15
17
|
@instance.groups.all.each &:destroy
|
16
18
|
@instance.users.all.each &:delete
|
17
19
|
end
|
18
20
|
|
21
|
+
let(:instance_name) { "a#{SecureRandom.hex(24)}" }
|
22
|
+
let(:group) { @instance.groups.create name: 'wheel' }
|
23
|
+
|
19
24
|
describe 'working with instances' do
|
20
25
|
subject { @api.instances }
|
21
26
|
|
22
27
|
it 'should raise an error on not found instance' do
|
23
|
-
expect { subject.find('kaszanka') }.to raise_error(Syncano::
|
28
|
+
expect { subject.find('kaszanka') }.to raise_error(Syncano::NotFound)
|
24
29
|
end
|
25
30
|
|
26
31
|
specify do
|
@@ -62,7 +67,6 @@ describe Syncano do
|
|
62
67
|
|
63
68
|
subject { @class.objects }
|
64
69
|
|
65
|
-
|
66
70
|
specify 'basic operations' do
|
67
71
|
expect { subject.create currency: 'USD', ballance: 1337 }.to create_resource
|
68
72
|
|
@@ -79,9 +83,6 @@ describe Syncano do
|
|
79
83
|
expect(object.currency).to eq('GBP')
|
80
84
|
|
81
85
|
expect { subject.destroy(object.primary_key) }.to destroy_resource
|
82
|
-
expect {
|
83
|
-
subject.destroy(object.primary_key)
|
84
|
-
}.to raise_error(Syncano::ClientError, /not found/i)
|
85
86
|
end
|
86
87
|
|
87
88
|
|
@@ -161,7 +162,7 @@ describe Syncano do
|
|
161
162
|
|
162
163
|
|
163
164
|
specify 'basic operations' do
|
164
|
-
expect { subject.create
|
165
|
+
expect { subject.create label: 'df', source: 'puts 1337', runtime_name: 'ruby' }.to create_resource
|
165
166
|
|
166
167
|
codebox = subject.first
|
167
168
|
codebox.run
|
@@ -169,7 +170,7 @@ describe Syncano do
|
|
169
170
|
codebox.save
|
170
171
|
codebox.run
|
171
172
|
|
172
|
-
without_profiling { sleep
|
173
|
+
without_profiling { sleep 10 }
|
173
174
|
traces = codebox.traces.all
|
174
175
|
|
175
176
|
expect(traces.count).to eq(2)
|
@@ -177,13 +178,13 @@ describe Syncano do
|
|
177
178
|
first = traces[1]
|
178
179
|
|
179
180
|
expect(first.status).to eq('success')
|
180
|
-
expect(first.result).to eq('1337')
|
181
|
+
expect(first.result["stdout"]).to eq('1337')
|
181
182
|
|
182
183
|
second = traces[0]
|
183
184
|
expect(second.status).to eq('success')
|
184
|
-
expect(second.result).to eq('123')
|
185
|
+
expect(second.result["stdout"]).to eq('123')
|
185
186
|
|
186
|
-
expect { @instance.schedules.create
|
187
|
+
expect { @instance.schedules.create label: 'test', interval_sec: 30, codebox: codebox.primary_key }.
|
187
188
|
to change { @instance.schedules.all.count }.by(1)
|
188
189
|
|
189
190
|
expect { codebox.destroy }.to destroy_resource
|
@@ -193,14 +194,218 @@ describe Syncano do
|
|
193
194
|
describe 'working with webhooks' do
|
194
195
|
subject { @instance.webhooks }
|
195
196
|
|
196
|
-
|
197
|
+
describe 'using the gem' do
|
198
|
+
let!(:codebox) { @instance.codeboxes.create label: 'wurst', source: 'puts "currywurst"', runtime_name: 'ruby' }
|
199
|
+
|
200
|
+
specify do
|
201
|
+
expect { subject.create name: 'web-wurst', codebox: codebox.primary_key }.to create_resource
|
202
|
+
|
203
|
+
expect(subject.first.run['result']["stdout"]).to eq('currywurst')
|
204
|
+
|
205
|
+
expect { subject.first.destroy }.to destroy_resource
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
describe 'using curl' do
|
210
|
+
let(:source) {
|
211
|
+
<<-SOURCE
|
212
|
+
p ARGS["POST"]
|
213
|
+
SOURCE
|
214
|
+
}
|
215
|
+
let!(:codebox) { @instance.codeboxes.create label: 'curl', source: source, runtime_name: 'ruby' }
|
216
|
+
let!(:webhook) { subject.create name: 'web-curl', codebox: codebox.primary_key, public: true }
|
217
|
+
|
218
|
+
specify do
|
219
|
+
url = "#{ENV['API_ROOT']}/v1/instances/#{instance_name}/webhooks/p/#{webhook.public_link}/"
|
220
|
+
code = %{curl -k --form kiszka=koza -H "kiszonka: 007" -X POST #{url} 2>/dev/null}
|
221
|
+
output = JSON.parse(`#{code}`)
|
222
|
+
|
223
|
+
expect(output["status"]).to eq("success")
|
224
|
+
expect(output["result"]["stdout"]).to eq('{"kiszka"=>"koza"}')
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
describe 'working with API keys' do
|
230
|
+
subject { @instance.api_keys }
|
231
|
+
|
232
|
+
specify do
|
233
|
+
api_key = nil
|
234
|
+
|
235
|
+
expect {
|
236
|
+
api_key = subject.create allow_user_create: true
|
237
|
+
}.to create_resource
|
238
|
+
|
239
|
+
expect { api_key.destroy }.to destroy_resource
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
describe 'managing users' do
|
244
|
+
subject { @instance.users }
|
245
|
+
|
246
|
+
let(:user_profile) { @instance.classes.find("user_profile") }
|
247
|
+
|
248
|
+
before do
|
249
|
+
user_profile.schema = [{ name: "nickname", type: "text" },
|
250
|
+
{ name: "resume", type: "file" }]
|
251
|
+
user_profile.save
|
252
|
+
end
|
253
|
+
|
254
|
+
specify do
|
255
|
+
user = nil
|
256
|
+
|
257
|
+
expect {
|
258
|
+
user = subject.create(username: 'koza', password: 'kiszkakoza')
|
259
|
+
}.to create_resource
|
260
|
+
|
261
|
+
user.update_attributes username: 'kiszka'
|
262
|
+
expect(subject.find(user.primary_key).username).to eq('kiszka')
|
263
|
+
|
264
|
+
|
265
|
+
profile = @instance.classes.find("user_profile").objects.find(1)
|
266
|
+
profile.nickname = "k0z4"
|
267
|
+
profile.resume = Syncano::UploadIO.new(File.absolute_path(__FILE__))
|
268
|
+
profile.save
|
269
|
+
|
270
|
+
expect(profile.nickname).to eq("k0z4")
|
271
|
+
|
272
|
+
expect { user.destroy }.to destroy_resource
|
273
|
+
end
|
274
|
+
end
|
275
|
+
|
276
|
+
|
277
|
+
describe 'managing groups' do
|
278
|
+
subject { @instance.groups }
|
279
|
+
|
280
|
+
specify do
|
281
|
+
creator = @instance.users.create username: 'content', password: 'creator'
|
282
|
+
|
283
|
+
content_creators = nil
|
284
|
+
|
285
|
+
expect {
|
286
|
+
content_creators = subject.create label: 'content creators'
|
287
|
+
}.to create_resource
|
288
|
+
|
289
|
+
expect {
|
290
|
+
content_creators.users.create user: creator.primary_key
|
291
|
+
}.to change { content_creators.users.all.count }.from(0).to(1)
|
292
|
+
|
293
|
+
expect { content_creators.destroy }.to destroy_resource
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
describe 'managing channels' do
|
298
|
+
subject do
|
299
|
+
@instance.channels
|
300
|
+
end
|
301
|
+
|
302
|
+
specify do
|
303
|
+
channel = nil
|
304
|
+
expect { channel = subject.create(name: 'chat') }.to create_resource
|
305
|
+
expect { channel.destroy }.to destroy_resource
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
|
310
|
+
describe 'subscribing to a channel' do
|
311
|
+
before(:each) { Celluloid.boot }
|
312
|
+
|
313
|
+
after(:each) { Celluloid.shutdown }
|
314
|
+
|
315
|
+
let!(:notifications) do
|
316
|
+
@instance.classes.create(name: 'notifications', schema: [{name: 'message', type: 'string'}]).objects
|
317
|
+
end
|
318
|
+
|
319
|
+
let!(:notifications_channel) do
|
320
|
+
@instance.channels.create name: 'system-notifications', other_permissions: 'subscribe'
|
321
|
+
end
|
322
|
+
|
323
|
+
specify do
|
324
|
+
poller = notifications_channel.poll
|
325
|
+
|
326
|
+
notification = notifications.create(message: "A new koza's arrived", channel: 'system-notifications')
|
327
|
+
|
328
|
+
sleep 20
|
329
|
+
|
330
|
+
expect(poller.responses.size).to eq(1)
|
331
|
+
expect(JSON.parse(poller.responses.last.body)["payload"]["message"]).to eq("A new koza's arrived")
|
332
|
+
|
333
|
+
notification.message = "A koza's gone"
|
334
|
+
notification.save
|
335
|
+
|
336
|
+
sleep 20
|
337
|
+
|
338
|
+
expect(poller.responses.size).to eq(2)
|
339
|
+
expect(JSON.parse(poller.responses.last.body)["payload"]["message"]).to eq("A koza's gone")
|
340
|
+
|
341
|
+
poller.terminate
|
342
|
+
end
|
343
|
+
end
|
344
|
+
|
345
|
+
describe 'subscribing to a room' do
|
346
|
+
before(:each) { Celluloid.boot }
|
347
|
+
|
348
|
+
after(:each) { Celluloid.shutdown }
|
349
|
+
|
350
|
+
let!(:house) {
|
351
|
+
@instance.channels.create name: 'house', type: 'separate_rooms', other_permissions: 'publish', custom_publish: true
|
352
|
+
}
|
353
|
+
let!(:shout) {
|
354
|
+
@instance.classes.create name: 'shout', schema: [{name: 'message', type: 'string'}]
|
355
|
+
}
|
356
|
+
|
357
|
+
specify do
|
358
|
+
shout.objects.create channel: 'house', channel_room: 'bathroom', message: "Where's the water?"
|
359
|
+
shout.objects.create channel: 'house', channel_room: 'basement', message: "Where's the light?"
|
360
|
+
|
361
|
+
expect(house.history.all(room: 'bathroom').count).to eq(1)
|
362
|
+
expect(house.history.all(room: 'basement').count).to eq(1)
|
363
|
+
expect(house.history.all.count).to eq(2)
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe 'using syncano on behalf of the user' do
|
368
|
+
let(:user_api_key) { @instance.api_keys.create.api_key }
|
369
|
+
let(:user) {
|
370
|
+
@instance.users.create username: 'kiszonka', password: 'passwd'
|
371
|
+
}
|
372
|
+
let(:another_user) {
|
373
|
+
@instance.users.create username: 'another', password: 'user'
|
374
|
+
}
|
375
|
+
let(:user_instance) {
|
376
|
+
Syncano.connect(api_key: user_api_key, user_key: user.user_key).
|
377
|
+
instances.first
|
378
|
+
}
|
379
|
+
let(:another_user_instance) {
|
380
|
+
Syncano.connect(api_key: user_api_key, user_key: another_user.user_key).
|
381
|
+
instances.first
|
382
|
+
}
|
383
|
+
let(:group) { @instance.groups.create label: 'content creators' }
|
384
|
+
|
385
|
+
before do
|
386
|
+
group.users.create user: user.primary_key
|
387
|
+
group.users.create user: another_user.primary_key
|
388
|
+
|
389
|
+
@instance.classes.create name: 'book',
|
390
|
+
schema: [{ name: 'title', type: 'string' }],
|
391
|
+
group: group.primary_key,
|
392
|
+
group_permissions: 'create_objects'
|
393
|
+
end
|
197
394
|
|
198
395
|
specify do
|
199
|
-
|
396
|
+
owner_books = user_instance.classes.find('book').objects
|
397
|
+
book = owner_books.create(title: 'Oliver Twist', owner_permissions: 'write')
|
398
|
+
|
399
|
+
expect(owner_books.all.to_a).to_not be_empty
|
400
|
+
|
401
|
+
group_member_books = another_user_instance.classes.find('book').objects
|
402
|
+
expect(group_member_books.all.to_a).to be_empty
|
200
403
|
|
201
|
-
|
404
|
+
book.group_permissions = 'read'
|
405
|
+
book.group = group.primary_key
|
406
|
+
book.save
|
202
407
|
|
203
|
-
expect
|
408
|
+
expect(group_member_books.all.to_a).to_not be_empty
|
204
409
|
end
|
205
410
|
end
|
206
411
|
|
data/spec/spec_helper.rb
CHANGED
@@ -7,6 +7,7 @@ Dotenv.load
|
|
7
7
|
require 'rspec-prof' if ENV['SPEC_PROFILE']
|
8
8
|
require 'syncano'
|
9
9
|
require 'webmock/rspec'
|
10
|
+
require 'celluloid/test'
|
10
11
|
|
11
12
|
WebMock.disable_net_connect!
|
12
13
|
|
@@ -16,4 +17,5 @@ end
|
|
16
17
|
|
17
18
|
def endpoint_uri(path)
|
18
19
|
[Syncano::Connection.api_root,"v1", path].join("/")
|
19
|
-
end
|
20
|
+
end
|
21
|
+
|