syncano 4.0.0.alpha4 → 4.0.0.pre

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/README.md +1 -13
  4. data/circle.yml +1 -1
  5. data/lib/active_attr/dirty.rb +26 -0
  6. data/lib/active_attr/typecasting/hash_typecaster.rb +34 -0
  7. data/lib/active_attr/typecasting_override.rb +29 -0
  8. data/lib/syncano.rb +9 -55
  9. data/lib/syncano/api.rb +2 -20
  10. data/lib/syncano/connection.rb +47 -48
  11. data/lib/syncano/model/associations.rb +121 -0
  12. data/lib/syncano/model/associations/base.rb +38 -0
  13. data/lib/syncano/model/associations/belongs_to.rb +30 -0
  14. data/lib/syncano/model/associations/has_many.rb +75 -0
  15. data/lib/syncano/model/associations/has_one.rb +22 -0
  16. data/lib/syncano/model/base.rb +257 -0
  17. data/lib/syncano/model/callbacks.rb +49 -0
  18. data/lib/syncano/model/scope_builder.rb +158 -0
  19. data/lib/syncano/query_builder.rb +7 -11
  20. data/lib/syncano/resources/base.rb +66 -91
  21. data/lib/syncano/schema.rb +159 -10
  22. data/lib/syncano/schema/attribute_definition.rb +0 -75
  23. data/lib/syncano/schema/resource_definition.rb +2 -24
  24. data/lib/syncano/version.rb +1 -1
  25. data/spec/integration/syncano_spec.rb +26 -268
  26. data/spec/spec_helper.rb +1 -3
  27. data/spec/unit/connection_spec.rb +74 -34
  28. data/spec/unit/query_builder_spec.rb +2 -2
  29. data/spec/unit/resources_base_spec.rb +64 -125
  30. data/spec/unit/schema/resource_definition_spec.rb +3 -24
  31. data/spec/unit/schema_spec.rb +55 -5
  32. data/spec/unit/syncano_spec.rb +9 -45
  33. data/syncano.gemspec +0 -5
  34. metadata +14 -87
  35. data/lib/syncano/api/endpoints.rb +0 -17
  36. data/lib/syncano/poller.rb +0 -55
  37. data/lib/syncano/resources.rb +0 -158
  38. data/lib/syncano/resources/paths.rb +0 -48
  39. data/lib/syncano/resources/resource_invalid.rb +0 -15
  40. data/lib/syncano/response.rb +0 -55
  41. data/lib/syncano/schema/endpoints_whitelist.rb +0 -40
  42. data/lib/syncano/upload_io.rb +0 -7
  43. data/spec/unit/resources/paths_spec.rb +0 -21
  44. data/spec/unit/response_spec.rb +0 -75
  45. data/spec/unit/schema/attribute_definition_spec.rb +0 -18
@@ -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 self.schema_path
13
- "/#{Syncano::Connection::API_VERSION}/schema/"
10
+ def initialize(connection)
11
+ self.connection = connection
12
+ load_schema
14
13
  end
15
14
 
16
- def initialize(connection = ::Syncano::Connection.new)
17
- self.connection = connection
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 definition
23
- raw_schema = connection.request(:get, self.class.schema_path)
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(name, raw_defitnition)
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
- Syncano::Schema::AttributeDefinition.new name, raw_attribute_definition
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
@@ -1,3 +1,3 @@
1
1
  module Syncano
2
- VERSION = '4.0.0.alpha4'
2
+ VERSION = '4.0.0.pre'
3
3
  end
@@ -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: instance_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::NotFound)
25
+ expect { subject.find('kaszanka') }.to raise_error(Syncano::ClientError)
29
26
  end
30
27
 
31
28
  specify do
32
- subject.create name: 'fafarafaa'
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 { subject.destroy(object.primary_key) }.to destroy_resource
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 label: 'df', source: 'puts 1337', runtime_name: 'ruby' }.to create_resource
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 10 }
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["stdout"]).to eq('1337')
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["stdout"]).to eq('123')
147
+ expect(second.result).to eq('123')
186
148
 
187
- expect { @instance.schedules.create label: 'test', interval_sec: 30, codebox: codebox.primary_key }.
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
- 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
159
+ let!(:codebox) { @instance.codeboxes.create name: 'wurst', source: 'puts "currywurst"', runtime_name: 'ruby' }
253
160
 
254
161
  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
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
- book.group_permissions = 'read'
405
- book.group = group.primary_key
406
- book.save
164
+ expect(subject.first.run['result']).to eq('currywurst')
407
165
 
408
- expect(group_member_books.all.to_a).to_not be_empty
166
+ expect { subject.first.destroy }.to destroy_resource
409
167
  end
410
168
  end
411
169