syncano 4.0.0.alpha4 → 4.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/README.md +1 -13
- data/circle.yml +1 -1
- data/lib/active_attr/dirty.rb +26 -0
- data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
- data/lib/active_attr/typecasting_override.rb +29 -0
- data/lib/syncano.rb +9 -55
- data/lib/syncano/api.rb +2 -20
- data/lib/syncano/connection.rb +47 -48
- data/lib/syncano/model/associations.rb +121 -0
- data/lib/syncano/model/associations/base.rb +38 -0
- data/lib/syncano/model/associations/belongs_to.rb +30 -0
- data/lib/syncano/model/associations/has_many.rb +75 -0
- data/lib/syncano/model/associations/has_one.rb +22 -0
- data/lib/syncano/model/base.rb +257 -0
- data/lib/syncano/model/callbacks.rb +49 -0
- data/lib/syncano/model/scope_builder.rb +158 -0
- data/lib/syncano/query_builder.rb +7 -11
- data/lib/syncano/resources/base.rb +66 -91
- data/lib/syncano/schema.rb +159 -10
- data/lib/syncano/schema/attribute_definition.rb +0 -75
- data/lib/syncano/schema/resource_definition.rb +2 -24
- data/lib/syncano/version.rb +1 -1
- data/spec/integration/syncano_spec.rb +26 -268
- data/spec/spec_helper.rb +1 -3
- data/spec/unit/connection_spec.rb +74 -34
- data/spec/unit/query_builder_spec.rb +2 -2
- data/spec/unit/resources_base_spec.rb +64 -125
- data/spec/unit/schema/resource_definition_spec.rb +3 -24
- data/spec/unit/schema_spec.rb +55 -5
- data/spec/unit/syncano_spec.rb +9 -45
- data/syncano.gemspec +0 -5
- metadata +14 -87
- data/lib/syncano/api/endpoints.rb +0 -17
- data/lib/syncano/poller.rb +0 -55
- data/lib/syncano/resources.rb +0 -158
- data/lib/syncano/resources/paths.rb +0 -48
- data/lib/syncano/resources/resource_invalid.rb +0 -15
- data/lib/syncano/response.rb +0 -55
- data/lib/syncano/schema/endpoints_whitelist.rb +0 -40
- data/lib/syncano/upload_io.rb +0 -7
- data/spec/unit/resources/paths_spec.rb +0 -21
- data/spec/unit/response_spec.rb +0 -75
- data/spec/unit/schema/attribute_definition_spec.rb +0 -18
data/lib/syncano/schema.rb
CHANGED
@@ -1,26 +1,33 @@
|
|
1
1
|
require_relative './schema/attribute_definition'
|
2
2
|
require_relative './schema/resource_definition'
|
3
|
-
require_relative './schema/endpoints_whitelist'
|
4
|
-
|
5
|
-
require 'singleton'
|
6
3
|
|
7
4
|
module Syncano
|
8
5
|
class Schema
|
6
|
+
SCHEMA_PATH = 'schema/'
|
9
7
|
|
10
8
|
attr_reader :schema
|
11
9
|
|
12
|
-
def
|
13
|
-
|
10
|
+
def initialize(connection)
|
11
|
+
self.connection = connection
|
12
|
+
load_schema
|
14
13
|
end
|
15
14
|
|
16
|
-
def
|
17
|
-
|
15
|
+
def process!
|
16
|
+
schema.each do |resource_name, resource_definition|
|
17
|
+
self.class.generate_resource_class(resource_name, resource_definition)
|
18
|
+
if resource_definition[:collection].present? && resource_definition[:collection][:path].scan(/\{([^}]+)\}/).empty?
|
19
|
+
self.class.generate_client_method(resource_name)
|
20
|
+
end
|
21
|
+
end
|
18
22
|
end
|
19
23
|
|
24
|
+
private
|
25
|
+
|
20
26
|
attr_accessor :connection
|
27
|
+
attr_writer :schema
|
21
28
|
|
22
|
-
def
|
23
|
-
raw_schema = connection.request(:get,
|
29
|
+
def load_schema
|
30
|
+
raw_schema = connection.request(:get, SCHEMA_PATH)
|
24
31
|
resources = {}
|
25
32
|
|
26
33
|
raw_schema.each do |resource_schema|
|
@@ -61,7 +68,149 @@ module Syncano
|
|
61
68
|
end
|
62
69
|
end
|
63
70
|
|
64
|
-
resources
|
71
|
+
self.schema = resources
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.generate_resource_class(name, definition_hash)
|
75
|
+
delete_colliding_links definition_hash
|
76
|
+
|
77
|
+
resource_definition = ::Syncano::Schema::ResourceDefinition.new(definition_hash)
|
78
|
+
resource_class = new_resource_class(resource_definition, name)
|
79
|
+
|
80
|
+
::Syncano::Resources.const_set(name, resource_class)
|
81
|
+
end
|
82
|
+
|
83
|
+
def self.delete_colliding_links(definition)
|
84
|
+
definition[:attributes].each do |k, v|
|
85
|
+
definition[:associations]['links'].delete_if { |link| link['name'] == k } if definition[:associations]['links']
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.new_resource_class(definition, name)
|
90
|
+
attributes_definitions = []
|
91
|
+
|
92
|
+
definition[:attributes].each do |attribute_name, attribute|
|
93
|
+
attributes_definitions << {
|
94
|
+
name: attribute_name,
|
95
|
+
type: map_syncano_attribute_type(attribute['type']),
|
96
|
+
default: attribute_name != 'channel' ? default_value_for_attribute(attribute) : nil,
|
97
|
+
presence_validation: attribute['required'],
|
98
|
+
length_validation_options: extract_length_validation_options(attribute),
|
99
|
+
inclusion_validation_options: extract_inclusion_validation_options(attribute),
|
100
|
+
create_writeable: attribute['read_only'] == false,
|
101
|
+
update_writeable: attribute['read_only'] == false,
|
102
|
+
}
|
103
|
+
end
|
104
|
+
|
105
|
+
::Class.new(::Syncano::Resources::Base) do
|
106
|
+
self.create_writable_attributes = []
|
107
|
+
self.update_writable_attributes = []
|
108
|
+
|
109
|
+
attributes_definitions.each do |attribute_definition|
|
110
|
+
attribute attribute_definition[:name], type: attribute_definition[:type], default: attribute_definition[:default], force_default: !attribute_definition[:default].nil?
|
111
|
+
validates attribute_definition[:name], presence: true if attribute_definition[:presence_validation]
|
112
|
+
validates attribute_definition[:name], length: attribute_definition[:length_validation_options]
|
113
|
+
|
114
|
+
if attribute_definition[:inclusion_validation_options]
|
115
|
+
validates attribute_definition[:name], inclusion: attribute_definition[:inclusion_validation_options]
|
116
|
+
end
|
117
|
+
|
118
|
+
self.create_writable_attributes << attribute_definition[:name].to_sym if attribute_definition[:create_writeable]
|
119
|
+
self.update_writable_attributes << attribute_definition[:name].to_sym if attribute_definition[:update_writeable]
|
120
|
+
end
|
121
|
+
|
122
|
+
if name == 'Object' #TODO: extract to a separate module + spec
|
123
|
+
attribute :custom_attributes, type: ::Object, default: nil, force_default: true
|
124
|
+
|
125
|
+
def attributes=(new_attributes)
|
126
|
+
super
|
127
|
+
|
128
|
+
self.custom_attributes = new_attributes.select { |k, v| !self.class.attributes.keys.include?(k) }
|
129
|
+
end
|
130
|
+
|
131
|
+
def method_missing(method_name, *args, &block)
|
132
|
+
if method_name.to_s =~ /=$/
|
133
|
+
custom_attributes[method_name.to_s.gsub(/=$/, '')] = args.first
|
134
|
+
else
|
135
|
+
if custom_attributes.has_key? method_name.to_s
|
136
|
+
custom_attributes[method_name]
|
137
|
+
else
|
138
|
+
super
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
(definition[:associations]['links'] || []).each do |association_schema|
|
145
|
+
if association_schema['type'] == 'list'
|
146
|
+
define_method(association_schema['name']) do
|
147
|
+
has_many_association(association_schema['name'])
|
148
|
+
end
|
149
|
+
elsif association_schema['type'] == 'detail' && association_schema['name'] != 'self'
|
150
|
+
define_method(association_schema['name']) do
|
151
|
+
belongs_to_association(association_schema['name'])
|
152
|
+
end
|
153
|
+
elsif association_schema['type'] == 'run'
|
154
|
+
define_method(association_schema['name']) do |config = nil|
|
155
|
+
custom_method association_schema['name'], config
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
private
|
161
|
+
|
162
|
+
self.resource_definition = definition
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def self.generate_client_method(resource_name)
|
167
|
+
method_name = resource_name.tableize
|
168
|
+
resource_class = "::Syncano::Resources::#{resource_name}".constantize
|
169
|
+
|
170
|
+
::Syncano::API.send(:define_method, method_name) do
|
171
|
+
::Syncano::QueryBuilder.new(connection, resource_class)
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
def self.extract_length_validation_options(attribute_definition)
|
176
|
+
maximum = begin
|
177
|
+
Integer attribute_definition['max_length']
|
178
|
+
rescue TypeError, ArgumentError
|
179
|
+
end
|
180
|
+
|
181
|
+
{ maximum: maximum } unless maximum.nil?
|
182
|
+
end
|
183
|
+
|
184
|
+
def self.extract_inclusion_validation_options(attribute_definition)
|
185
|
+
return unless choices = attribute_definition['choices']
|
186
|
+
|
187
|
+
{ in: choices.map { |choice| choice['value'] } }
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.map_syncano_attribute_type(type)
|
191
|
+
mapping = HashWithIndifferentAccess.new(
|
192
|
+
string: ::String,
|
193
|
+
email: ::String,
|
194
|
+
choice: ::String,
|
195
|
+
slug: ::String,
|
196
|
+
integer: ::Integer,
|
197
|
+
float: ::Float,
|
198
|
+
date: ::Date,
|
199
|
+
datetime: ::DateTime,
|
200
|
+
field: ::Object
|
201
|
+
)
|
202
|
+
|
203
|
+
type.present? ? mapping[type] : Object
|
204
|
+
end
|
205
|
+
|
206
|
+
def self.default_value_for_attribute(attribute)
|
207
|
+
if attribute['type'].present? && attribute['type'].to_sym == :field
|
208
|
+
{}
|
209
|
+
elsif attribute['type'].present? && attribute['type'].to_sym == :choice
|
210
|
+
attribute['choices'].first['value']
|
211
|
+
else
|
212
|
+
nil
|
213
|
+
end
|
65
214
|
end
|
66
215
|
end
|
67
216
|
end
|
@@ -1,82 +1,7 @@
|
|
1
1
|
module Syncano
|
2
2
|
class Schema
|
3
3
|
class AttributeDefinition
|
4
|
-
attr_accessor :name, :type, :default
|
5
|
-
|
6
|
-
TYPES_MAPPING = { 'string' => ::String,
|
7
|
-
'email' => ::String,
|
8
|
-
'choice' => ::String,
|
9
|
-
'slug' => ::String,
|
10
|
-
'integer' => ::Integer,
|
11
|
-
'float' => ::Float,
|
12
|
-
'date' => ::Date,
|
13
|
-
'datetime' => ::DateTime,
|
14
|
-
'field' => ::Object }
|
15
|
-
|
16
4
|
def initialize(name, raw_definition)
|
17
|
-
# TODO implement #original_name to send request with correct parameters
|
18
|
-
self.name = name == 'class' ? 'associated_class' : name
|
19
|
-
self.raw_definition = raw_definition
|
20
|
-
|
21
|
-
set_type
|
22
|
-
set_default
|
23
|
-
end
|
24
|
-
|
25
|
-
def validate?
|
26
|
-
writable? && required?
|
27
|
-
end
|
28
|
-
|
29
|
-
def writable?
|
30
|
-
raw_definition['read_only'] == false
|
31
|
-
end
|
32
|
-
|
33
|
-
def required?
|
34
|
-
raw_definition['required'] == true
|
35
|
-
end
|
36
|
-
|
37
|
-
def required_length
|
38
|
-
begin
|
39
|
-
{ maximum: Integer(raw_definition['max_length']) }
|
40
|
-
rescue TypeError, ArgumentError
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
def required_values_inclusion
|
45
|
-
return unless choices = raw_definition['choices']
|
46
|
-
|
47
|
-
{ in: choices.map { |choice| choice['value'] } }
|
48
|
-
end
|
49
|
-
|
50
|
-
alias :updatable? :writable?
|
51
|
-
|
52
|
-
def [](key)
|
53
|
-
raw_definition[key]
|
54
|
-
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
attr_accessor :raw_definition
|
59
|
-
|
60
|
-
def set_type
|
61
|
-
self.type = if %w[owner group].include?(name)
|
62
|
-
::Integer
|
63
|
-
elsif raw_definition['type'].blank?
|
64
|
-
::Object
|
65
|
-
else
|
66
|
-
TYPES_MAPPING[raw_definition['type']]
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
def set_default
|
71
|
-
self.default = if name == 'channel'
|
72
|
-
nil
|
73
|
-
elsif raw_definition['type'].present? && raw_definition['type'].to_sym == :field
|
74
|
-
{}
|
75
|
-
elsif raw_definition['type'].present? && raw_definition['type'].to_sym == :choice
|
76
|
-
raw_definition['choices'].try(:first).try :[], 'value'
|
77
|
-
else
|
78
|
-
nil
|
79
|
-
end
|
80
5
|
end
|
81
6
|
end
|
82
7
|
end
|
@@ -2,40 +2,18 @@ module Syncano
|
|
2
2
|
class Schema
|
3
3
|
class ResourceDefinition
|
4
4
|
attr_accessor :attributes
|
5
|
-
attr_accessor :name
|
6
5
|
|
7
|
-
def initialize(
|
6
|
+
def initialize(raw_defitnition)
|
8
7
|
@raw_definition = raw_defitnition
|
9
8
|
|
10
|
-
delete_colliding_links
|
11
|
-
|
12
|
-
self.name = name
|
13
|
-
|
14
9
|
self.attributes = raw_defitnition[:attributes].map do |name, raw_attribute_definition|
|
15
|
-
|
10
|
+
AttributeDefinition.new name, raw_attribute_definition
|
16
11
|
end
|
17
12
|
end
|
18
13
|
|
19
14
|
def [](key)
|
20
15
|
@raw_definition[key]
|
21
16
|
end
|
22
|
-
|
23
|
-
def top_level?
|
24
|
-
@raw_definition[:collection].present? &&
|
25
|
-
@raw_definition[:collection][:path].scan(/\{([^}]+)\}/).empty?
|
26
|
-
end
|
27
|
-
|
28
|
-
private
|
29
|
-
|
30
|
-
def delete_colliding_links
|
31
|
-
@raw_definition[:attributes].each do |k, v|
|
32
|
-
if @raw_definition[:associations]['links']
|
33
|
-
@raw_definition[:associations]['links'].delete_if do |link|
|
34
|
-
link['name'] == k
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
39
17
|
end
|
40
18
|
end
|
41
19
|
end
|
data/lib/syncano/version.rb
CHANGED
@@ -4,32 +4,29 @@ WebMock.allow_net_connect!
|
|
4
4
|
|
5
5
|
describe Syncano do
|
6
6
|
before(:all) do
|
7
|
-
Syncano::API.send :initialized=, false
|
8
|
-
|
9
7
|
@api_key = ENV['INTEGRATION_TEST_API_KEY']
|
10
8
|
@api = Syncano.connect(api_key: @api_key)
|
11
9
|
end
|
12
10
|
|
13
11
|
before(:each) do
|
14
12
|
@api.instances.all.each &:destroy
|
15
|
-
@instance = @api.instances.create(name:
|
13
|
+
@instance = @api.instances.create(name: "a#{@api_key}")
|
16
14
|
@instance.classes.all.select { |c| c.name != 'user_profile'}.each &:destroy
|
17
15
|
@instance.groups.all.each &:destroy
|
18
16
|
@instance.users.all.each &:delete
|
19
17
|
end
|
20
18
|
|
21
|
-
let(:instance_name) { "a#{SecureRandom.hex(24)}" }
|
22
19
|
let(:group) { @instance.groups.create name: 'wheel' }
|
23
20
|
|
24
21
|
describe 'working with instances' do
|
25
22
|
subject { @api.instances }
|
26
23
|
|
27
24
|
it 'should raise an error on not found instance' do
|
28
|
-
expect { subject.find('kaszanka') }.to raise_error(Syncano::
|
25
|
+
expect { subject.find('kaszanka') }.to raise_error(Syncano::ClientError)
|
29
26
|
end
|
30
27
|
|
31
28
|
specify do
|
32
|
-
subject.create name: '
|
29
|
+
subject.create name: 'fafarafa'
|
33
30
|
end
|
34
31
|
end
|
35
32
|
|
@@ -37,7 +34,7 @@ describe Syncano do
|
|
37
34
|
subject { @instance.classes }
|
38
35
|
|
39
36
|
specify do
|
40
|
-
expect { subject.create name: 'sausage', schema: [{name: 'name', type: 'string' }] }.to create_resource
|
37
|
+
expect { subject.create name: 'sausage', schema: [{name: 'name', type: 'string' }], group: group.primary_key }.to create_resource
|
41
38
|
|
42
39
|
new_klass = subject.last
|
43
40
|
|
@@ -60,15 +57,18 @@ describe Syncano do
|
|
60
57
|
|
61
58
|
describe 'working with objects' do
|
62
59
|
before do
|
60
|
+
@owner = @instance.users.create username: 'admin', password: 'dupa.8'
|
63
61
|
@class = @instance.classes.create name: 'account',
|
62
|
+
group: group.primary_key,
|
64
63
|
schema: [{name: 'currency', type: 'string', filter_index: true},
|
65
64
|
{name: 'ballance', type: 'integer', filter_index: true, order_index: true}]
|
66
65
|
end
|
67
66
|
|
68
67
|
subject { @class.objects }
|
69
68
|
|
69
|
+
|
70
70
|
specify 'basic operations' do
|
71
|
-
expect { subject.create currency: 'USD', ballance: 1337 }.to create_resource
|
71
|
+
expect { subject.create currency: 'USD', ballance: 1337, group: group.primary_key, owner: @owner.primary_key }.to create_resource
|
72
72
|
|
73
73
|
object = subject.first
|
74
74
|
|
@@ -82,63 +82,25 @@ describe Syncano do
|
|
82
82
|
expect(object.ballance).to eq(54)
|
83
83
|
expect(object.currency).to eq('GBP')
|
84
84
|
|
85
|
-
expect {
|
86
|
-
end
|
87
|
-
|
88
|
-
|
89
|
-
specify 'PATH and POST' do
|
90
|
-
initial_yuan = subject.create currency: 'CNY', ballance: 98123
|
91
|
-
|
92
|
-
yuan = subject.first
|
93
|
-
new_yuan = subject.first
|
94
|
-
|
95
|
-
yuan.ballance = 100000
|
96
|
-
yuan.save
|
97
|
-
|
98
|
-
new_yuan.currency = 'RMB'
|
99
|
-
new_yuan.save
|
100
|
-
|
101
|
-
yuan = subject.first
|
102
|
-
|
103
|
-
expect(yuan.currency).to eq('RMB')
|
104
|
-
expect(yuan.ballance).to eq(100000)
|
105
|
-
|
106
|
-
initial_yuan.save(overwrite: true)
|
107
|
-
yuan.reload!
|
108
|
-
|
109
|
-
expect(yuan.currency).to eq('CNY')
|
110
|
-
expect(yuan.ballance).to eq(98123)
|
85
|
+
expect { object.destroy }.to destroy_resource
|
111
86
|
end
|
112
87
|
|
113
88
|
specify 'filtering and ordering' do
|
114
|
-
usd = subject.create(currency: 'USD', ballance: 400)
|
115
|
-
pln = subject.create(currency: 'PLN', ballance: 1600)
|
116
|
-
eur = subject.create(currency: 'EUR', ballance: 400)
|
117
|
-
gbp = subject.create(currency: 'GPB', ballance: 270)
|
118
|
-
chf = subject.create(currency: 'CHF', ballance: 390)
|
119
|
-
uah = subject.create(currency: 'UAH', ballance: 9100)
|
120
|
-
rub = subject.create(currency: 'RUB')
|
89
|
+
usd = subject.create(currency: 'USD', ballance: 400, group: group.primary_key, owner: @owner.primary_key)
|
90
|
+
pln = subject.create(currency: 'PLN', ballance: 1600, group: group.primary_key, owner: @owner.primary_key)
|
91
|
+
eur = subject.create(currency: 'EUR', ballance: 400, group: group.primary_key, owner: @owner.primary_key)
|
92
|
+
gbp = subject.create(currency: 'GPB', ballance: 270, group: group.primary_key, owner: @owner.primary_key)
|
93
|
+
chf = subject.create(currency: 'CHF', ballance: 390, group: group.primary_key, owner: @owner.primary_key)
|
94
|
+
uah = subject.create(currency: 'UAH', ballance: 9100, group: group.primary_key, owner: @owner.primary_key)
|
95
|
+
rub = subject.create(currency: 'RUB', group: group.primary_key, owner: @owner.primary_key)
|
121
96
|
|
122
97
|
expect(subject.all(query: { ballance: { _exists: true }}).to_a).to_not include(rub)
|
123
98
|
expect(subject.all(query: { currency: { _in: %w[UAH USD PLN] } }).to_a).to match_array([pln, usd, uah])
|
124
99
|
expect(subject.all(query: { ballance: { _lt: 400, _gte: 270 }}, order_by: '-ballance').to_a).to eq([chf, gbp])
|
125
100
|
end
|
126
101
|
|
127
|
-
specify 'fetching only specific fields' do
|
128
|
-
subject.create(currency: 'USD', ballance: 400)
|
129
|
-
|
130
|
-
account = subject.all(fields: 'currency').first
|
131
|
-
expect { account.currency }.to_not raise_error
|
132
|
-
expect { account.ballance }.to raise_error(NoMethodError)
|
133
|
-
|
134
|
-
|
135
|
-
account = subject.first(excluded_fields: 'currency')
|
136
|
-
expect { account.currency }.to raise_error(NoMethodError)
|
137
|
-
expect { account.ballance }.to_not raise_error
|
138
|
-
end
|
139
|
-
|
140
102
|
specify 'paging', slow: true do
|
141
|
-
104.times { subject.create }
|
103
|
+
104.times { subject.create group: group.primary_key, owner: @owner.primary_key }
|
142
104
|
|
143
105
|
total = 0
|
144
106
|
all = subject.all
|
@@ -162,7 +124,7 @@ describe Syncano do
|
|
162
124
|
|
163
125
|
|
164
126
|
specify 'basic operations' do
|
165
|
-
expect { subject.create
|
127
|
+
expect { subject.create name: 'df', source: 'puts 1337', runtime_name: 'ruby' }.to create_resource
|
166
128
|
|
167
129
|
codebox = subject.first
|
168
130
|
codebox.run
|
@@ -170,7 +132,7 @@ describe Syncano do
|
|
170
132
|
codebox.save
|
171
133
|
codebox.run
|
172
134
|
|
173
|
-
without_profiling { sleep
|
135
|
+
without_profiling { sleep 5 }
|
174
136
|
traces = codebox.traces.all
|
175
137
|
|
176
138
|
expect(traces.count).to eq(2)
|
@@ -178,13 +140,13 @@ describe Syncano do
|
|
178
140
|
first = traces[1]
|
179
141
|
|
180
142
|
expect(first.status).to eq('success')
|
181
|
-
expect(first.result
|
143
|
+
expect(first.result).to eq('1337')
|
182
144
|
|
183
145
|
second = traces[0]
|
184
146
|
expect(second.status).to eq('success')
|
185
|
-
expect(second.result
|
147
|
+
expect(second.result).to eq('123')
|
186
148
|
|
187
|
-
expect { @instance.schedules.create
|
149
|
+
expect { @instance.schedules.create name: 'test', interval_sec: 30, codebox: codebox.primary_key }.
|
188
150
|
to change { @instance.schedules.all.count }.by(1)
|
189
151
|
|
190
152
|
expect { codebox.destroy }.to destroy_resource
|
@@ -194,218 +156,14 @@ describe Syncano do
|
|
194
156
|
describe 'working with webhooks' do
|
195
157
|
subject { @instance.webhooks }
|
196
158
|
|
197
|
-
|
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
|
159
|
+
let!(:codebox) { @instance.codeboxes.create name: 'wurst', source: 'puts "currywurst"', runtime_name: 'ruby' }
|
253
160
|
|
254
161
|
specify do
|
255
|
-
|
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
|
394
|
-
|
395
|
-
specify do
|
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
|
162
|
+
expect { subject.create slug: 'web-wurst', codebox: codebox.primary_key }.to create_resource
|
403
163
|
|
404
|
-
|
405
|
-
book.group = group.primary_key
|
406
|
-
book.save
|
164
|
+
expect(subject.first.run['result']).to eq('currywurst')
|
407
165
|
|
408
|
-
expect
|
166
|
+
expect { subject.first.destroy }.to destroy_resource
|
409
167
|
end
|
410
168
|
end
|
411
169
|
|