ably 0.1.4 → 0.1.5

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/ably.gemspec +1 -0
  3. data/lib/ably/auth.rb +9 -13
  4. data/lib/ably/models/idiomatic_ruby_wrapper.rb +27 -39
  5. data/lib/ably/modules/conversions.rb +31 -10
  6. data/lib/ably/modules/enum.rb +201 -0
  7. data/lib/ably/modules/event_emitter.rb +81 -0
  8. data/lib/ably/modules/event_machine_helpers.rb +21 -0
  9. data/lib/ably/modules/http_helpers.rb +13 -0
  10. data/lib/ably/modules/state.rb +67 -0
  11. data/lib/ably/realtime.rb +6 -1
  12. data/lib/ably/realtime/channel.rb +117 -56
  13. data/lib/ably/realtime/client.rb +7 -50
  14. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +116 -0
  15. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +63 -0
  16. data/lib/ably/realtime/connection.rb +97 -14
  17. data/lib/ably/realtime/models/error_info.rb +3 -2
  18. data/lib/ably/realtime/models/message.rb +28 -3
  19. data/lib/ably/realtime/models/nil_channel.rb +21 -0
  20. data/lib/ably/realtime/models/protocol_message.rb +35 -27
  21. data/lib/ably/rest/client.rb +39 -23
  22. data/lib/ably/rest/middleware/external_exceptions.rb +1 -1
  23. data/lib/ably/rest/middleware/parse_json.rb +7 -2
  24. data/lib/ably/rest/middleware/parse_message_pack.rb +23 -0
  25. data/lib/ably/rest/models/paged_resource.rb +4 -4
  26. data/lib/ably/util/pub_sub.rb +32 -0
  27. data/lib/ably/version.rb +1 -1
  28. data/spec/acceptance/realtime/channel_spec.rb +1 -0
  29. data/spec/acceptance/realtime/message_spec.rb +136 -0
  30. data/spec/acceptance/rest/base_spec.rb +51 -1
  31. data/spec/acceptance/rest/presence_spec.rb +7 -2
  32. data/spec/integration/modules/state_spec.rb +66 -0
  33. data/spec/{unit → integration/rest}/auth.rb +0 -0
  34. data/spec/support/api_helper.rb +5 -2
  35. data/spec/support/protocol_msgbus_helper.rb +29 -0
  36. data/spec/support/test_app.rb +14 -3
  37. data/spec/unit/{conversions.rb → modules/conversions_spec.rb} +1 -1
  38. data/spec/unit/modules/enum_spec.rb +263 -0
  39. data/spec/unit/modules/event_emitter_spec.rb +81 -0
  40. data/spec/unit/modules/pub_sub_spec.rb +74 -0
  41. data/spec/unit/realtime/channel_spec.rb +27 -0
  42. data/spec/unit/realtime/client_spec.rb +8 -0
  43. data/spec/unit/realtime/connection_spec.rb +40 -0
  44. data/spec/unit/realtime/error_info_spec.rb +9 -1
  45. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  46. data/spec/unit/realtime/message_spec.rb +2 -2
  47. data/spec/unit/realtime/protocol_message_spec.rb +78 -9
  48. data/spec/unit/rest/{rest_spec.rb → client_spec.rb} +0 -0
  49. data/spec/unit/rest/message_spec.rb +1 -1
  50. metadata +51 -9
  51. data/lib/ably/realtime/callbacks.rb +0 -15
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ably::Modules::State do
4
+ class ExampleStateWithEventEmitter
5
+ include Ably::Modules::EventEmitter
6
+ extend Ably::Modules::Enum
7
+
8
+ STATE = ruby_enum('STATE',
9
+ :initializing,
10
+ :connecting,
11
+ :connected,
12
+ :disconnected
13
+ )
14
+ include Ably::Modules::State
15
+
16
+ def initialize
17
+ @state = :initializing
18
+ end
19
+ end
20
+
21
+ let(:initial_state) { :initializing }
22
+
23
+ subject { ExampleStateWithEventEmitter.new }
24
+
25
+ specify '#state returns current state' do
26
+ expect(subject.state).to eq(:initializing)
27
+ end
28
+
29
+ specify '#state= sets current state' do
30
+ expect { subject.state = :connecting }.to change { subject.state }.to(:connecting)
31
+ end
32
+
33
+ specify '#change_state sets current state' do
34
+ expect { subject.change_state :connecting }.to change { subject.state }.to(:connecting)
35
+ end
36
+
37
+ context '#state?' do
38
+ it 'returns true if state matches' do
39
+ expect(subject.state?(initial_state)).to eql(true)
40
+ end
41
+
42
+ it 'returns false if state does not match' do
43
+ expect(subject.state?(:connecting)).to eql(false)
44
+ end
45
+
46
+ context 'and convenience predicates for states' do
47
+ it 'returns true if state matches' do
48
+ expect(subject.initializing?).to eql(true)
49
+ end
50
+
51
+ it 'returns false if state does not match' do
52
+ expect(subject.connecting?).to eql(false)
53
+ end
54
+ end
55
+ end
56
+
57
+ context '#state STATE coercion' do
58
+ it 'allows valid STATE values' do
59
+ expect { subject.state = :connected }.to_not raise_error
60
+ end
61
+
62
+ it 'prevents invalid STATE values' do
63
+ expect { subject.state = :invalid }.to raise_error KeyError
64
+ end
65
+ end
66
+ end
File without changes
@@ -17,6 +17,10 @@ module ApiHelper
17
17
  TestApp.instance.api_key
18
18
  end
19
19
 
20
+ def restricted_api_key
21
+ TestApp.instance.restricted_api_key
22
+ end
23
+
20
24
  def environment
21
25
  TestApp.instance.environment
22
26
  end
@@ -31,11 +35,10 @@ RSpec.configure do |config|
31
35
 
32
36
  config.before(:suite) do
33
37
  WebMock.disable!
34
- TestApp.instance
35
38
  end
36
39
 
37
40
  config.after(:suite) do
38
41
  WebMock.disable!
39
- TestApp.instance.delete
42
+ TestApp.instance.delete if TestApp.instance_variable_get('@singleton__instance__')
40
43
  end
41
44
  end
@@ -0,0 +1,29 @@
1
+ shared_examples 'a protocol message bus' do
2
+ describe '__protocol_msgbus__ PubSub' do
3
+ let(:message) { double(:message, name: 'name', channel: 'channel', messages: []) }
4
+
5
+ specify 'supports valid ProtocolMessage messages' do
6
+ received = 0
7
+ msgbus.subscribe(:message) { received += 1 }
8
+ expect { msgbus.publish(:message, message) }.to change { received }.to(1)
9
+ end
10
+
11
+ specify 'fail with unacceptable STATE event names' do
12
+ expect { msgbus.subscribe(:invalid) }.to raise_error KeyError
13
+ expect { msgbus.publish(:invalid) }.to raise_error KeyError
14
+ expect { msgbus.unsubscribe(:invalid) }.to raise_error KeyError
15
+ end
16
+ end
17
+ end
18
+
19
+ shared_examples 'an incoming protocol message bus' do
20
+ it_behaves_like 'a protocol message bus' do
21
+ let(:msgbus) { subject.__incoming_protocol_msgbus__ }
22
+ end
23
+ end
24
+
25
+ shared_examples 'an outgoing protocol message bus' do
26
+ it_behaves_like 'a protocol message bus' do
27
+ let(:msgbus) { subject.__outgoing_protocol_msgbus__ }
28
+ end
29
+ end
@@ -3,7 +3,10 @@ require "singleton"
3
3
  class TestApp
4
4
  APP_SPEC = {
5
5
  'keys' => [
6
- {}
6
+ {},
7
+ {
8
+ 'capability' => '{ "*":["subscribe"], "canpublish:*":["publish"], "canpublish:andpresence":["presence","publish"] }'
9
+ }
7
10
  ],
8
11
  'namespaces' => [
9
12
  { 'id' => 'persisted', 'persisted' => true }
@@ -44,18 +47,26 @@ class TestApp
44
47
  @attributes["keys"].first
45
48
  end
46
49
 
50
+ def restricted_key
51
+ @attributes["keys"][1]
52
+ end
53
+
47
54
  def key_id
48
- "#{app_id}.#{key["id"]}"
55
+ "#{app_id}.#{key['id']}"
49
56
  end
50
57
 
51
58
  def key_value
52
- key["value"]
59
+ key['value']
53
60
  end
54
61
 
55
62
  def api_key
56
63
  "#{key_id}:#{key_value}"
57
64
  end
58
65
 
66
+ def restricted_api_key
67
+ "#{app_id}.#{restricted_key['id']}:#{restricted_key['value']}"
68
+ end
69
+
59
70
  def delete
60
71
  url = "#{sandbox_client.endpoint}/apps/#{app_id}"
61
72
 
@@ -1,4 +1,4 @@
1
- require "spec_helper"
1
+ require 'spec_helper'
2
2
 
3
3
  describe Ably::Modules::Conversions do
4
4
  let(:class_with_module) { Class.new do; include Ably::Modules::Conversions; end }
@@ -0,0 +1,263 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe Ably::Modules::Enum do
5
+ class ExampleClassWithEnum
6
+ extend Ably::Modules::Enum
7
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :value_zero, 'value_1', :value_snake_case_2, :SentenceCase)
8
+ end
9
+
10
+ let(:enum) { ExampleClassWithEnum::ENUMEXAMPLE }
11
+ let(:enum_name) { 'ENUMEXAMPLE' }
12
+
13
+ context 'convertor method added to class' do
14
+ subject { ExampleClassWithEnum }
15
+
16
+ it 'converts symbols' do
17
+ expect(subject.ENUMEXAMPLE(:value_zero)).to eql(enum.get(:value_zero))
18
+ end
19
+
20
+ it 'converts strings' do
21
+ expect(subject.ENUMEXAMPLE('ValueZero')).to eql(enum.get(:value_zero))
22
+ end
23
+
24
+ it 'converts integer index' do
25
+ expect(subject.ENUMEXAMPLE(0)).to eql(enum.get(:value_zero))
26
+ end
27
+ end
28
+
29
+ context 'convertor method added to instance' do
30
+ subject { ExampleClassWithEnum.new }
31
+
32
+ it 'converts symbols' do
33
+ expect(subject.ENUMEXAMPLE(:value_zero)).to eql(enum.get(:value_zero))
34
+ end
35
+
36
+ it 'converts strings' do
37
+ expect(subject.ENUMEXAMPLE('ValueZero')).to eql(enum.get(:value_zero))
38
+ end
39
+
40
+ it 'converts integer index' do
41
+ expect(subject.ENUMEXAMPLE(0)).to eql(enum.get(:value_zero))
42
+ end
43
+ end
44
+
45
+ context 'defined Enum from Array class' do
46
+ subject { enum }
47
+
48
+ it 'provides a MixedCase const for each provided value' do
49
+ expect(subject.ValueZero).to be_a(subject)
50
+ expect(subject.ValueSnakeCase2).to be_a(subject)
51
+ end
52
+
53
+ context '#get' do
54
+ context 'by integer index' do
55
+ let(:return_val) { subject.get(0) }
56
+ it 'returns an enum' do
57
+ expect(return_val).to be_a(subject)
58
+ expect(return_val.to_sym).to eql(:value_zero)
59
+ end
60
+ end
61
+
62
+ context 'by string value' do
63
+ let(:return_val) { subject.get('value_zero') }
64
+ it 'returns an enum' do
65
+ expect(return_val).to be_a(subject)
66
+ expect(return_val.to_sym).to eql(:value_zero)
67
+ end
68
+ end
69
+
70
+ context 'by symbol' do
71
+ let(:return_val) { subject.get(:sentence_case) }
72
+ it 'returns an enum' do
73
+ expect(return_val).to be_a(subject)
74
+ expect(return_val.to_sym).to eql(:sentence_case)
75
+ end
76
+ end
77
+
78
+ context 'by enum' do
79
+ let(:return_val) { subject.get(subject.get(:sentence_case)) }
80
+ it 'returns an enum' do
81
+ expect(return_val).to be_a(subject)
82
+ expect(return_val.to_sym).to eql(:sentence_case)
83
+ end
84
+ end
85
+
86
+ context 'by invalid type' do
87
+ let(:return_val) { subject.get(Array.new) }
88
+ it 'raises an error' do
89
+ expect { return_val }.to raise_error KeyError
90
+ end
91
+ end
92
+ end
93
+
94
+ context '#[]' do
95
+ let(:argument) { SecureRandom.hex }
96
+ before do
97
+ expect(subject).to receive(:get).with(argument).once.and_return(true)
98
+ end
99
+
100
+ it 'is an alias for get' do
101
+ subject[argument]
102
+ end
103
+ end
104
+
105
+ it 'returns the provided Enum name' do
106
+ expect(subject.name).to eql(enum_name)
107
+ end
108
+
109
+ specify '#to_s returns the Enum name' do
110
+ expect("#{subject}").to eql(enum_name)
111
+ end
112
+
113
+ context '#size' do
114
+ it 'returns the number of enum items' do
115
+ expect(subject.size).to eql(4)
116
+ end
117
+
118
+ it 'has alias #length' do
119
+ expect(subject.length).to eql(subject.size)
120
+ end
121
+ end
122
+
123
+ it 'freezes the Enum' do
124
+ expect(subject.send(:by_index)).to be_frozen
125
+ end
126
+
127
+ it 'prevents modification' do
128
+ expect { subject.send(:define_values, :enum_id) }.to raise_error RuntimeError, /cannot be modified/
129
+ end
130
+
131
+ it 'behaves like Enumerable' do
132
+ expect(subject.map(&:to_sym)).to eql([:value_zero, :value_1, :value_snake_case_2, :sentence_case])
133
+ end
134
+ end
135
+
136
+ context 'defined Enum from Hash class' do
137
+ class ExampleClassWithEnumFromHash
138
+ extend Ably::Modules::Enum
139
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE',
140
+ value_one: 1,
141
+ value_five: 5
142
+ )
143
+ end
144
+
145
+ subject { ExampleClassWithEnumFromHash::ENUMEXAMPLE }
146
+
147
+ it 'provides a MixedCase const for each provided value' do
148
+ expect(subject.ValueOne).to be_a(subject)
149
+ expect(subject.ValueFive).to be_a(subject)
150
+ end
151
+
152
+ context '#get' do
153
+ context 'by integer index for 1' do
154
+ let(:return_val) { subject.get(1) }
155
+ it 'returns an enum' do
156
+ expect(return_val).to be_a(subject)
157
+ expect(return_val.to_sym).to eql(:value_one)
158
+ end
159
+ end
160
+
161
+ context 'by integer index for 5' do
162
+ let(:return_val) { subject.get(5) }
163
+ it 'returns an enum' do
164
+ expect(return_val).to be_a(subject)
165
+ expect(return_val.to_sym).to eql(:value_five)
166
+ end
167
+ end
168
+
169
+ context 'by invalid integer index' do
170
+ let(:return_val) { subject.get(0) }
171
+ it 'raises an exception' do
172
+ expect { return_val }.to raise_error KeyError
173
+ end
174
+ end
175
+
176
+ context 'by string value' do
177
+ let(:return_val) { subject.get('value_five') }
178
+ it 'returns an enum' do
179
+ expect(return_val).to be_a(subject)
180
+ expect(return_val.to_sym).to eql(:value_five)
181
+ expect(return_val.to_i).to eql(5)
182
+ end
183
+ end
184
+ end
185
+ end
186
+
187
+ context 'defined Enum from another Enum' do
188
+ class ExampleBaseEnum
189
+ extend Ably::Modules::Enum
190
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', :one, :second_enum)
191
+ end
192
+
193
+ class ExampleOtherEnum
194
+ extend Ably::Modules::Enum
195
+ ENUMEXAMPLE = ruby_enum('ENUMEXAMPLE', ExampleBaseEnum::ENUMEXAMPLE)
196
+ end
197
+
198
+ subject { ExampleOtherEnum::ENUMEXAMPLE }
199
+
200
+ it 'provides a MixedCase const for each provided value' do
201
+ expect(subject.One).to be_a(subject)
202
+ expect(subject.SecondEnum).to be_a(subject)
203
+ end
204
+ end
205
+
206
+ context 'Enum instance' do
207
+ context '#==' do
208
+ subject { enum.get(:value_snake_case_2) }
209
+
210
+ it 'compares with a symbol' do
211
+ expect(subject).to eq(:value_Snake_Case_2)
212
+ end
213
+
214
+ it 'compares with a string' do
215
+ expect(subject).to eq('ValueSnakeCase2')
216
+ end
217
+
218
+ it 'compares with a integer index' do
219
+ expect(subject).to eq(subject.to_i)
220
+ end
221
+
222
+ it 'compares with itself' do
223
+ expect(subject).to eq(subject)
224
+ end
225
+
226
+ it 'compares with other Objects' do
227
+ expect(subject).to_not eq(Object.new)
228
+ end
229
+ end
230
+
231
+ context '#to_s' do
232
+ subject { enum.get(:value_zero) }
233
+
234
+ it 'returns ENUMNAME.CamelCase name' do
235
+ expect(subject.to_s).to eql("#{enum_name}.ValueZero")
236
+ end
237
+ end
238
+
239
+ context '#to_sym' do
240
+ subject { enum.get(:value_zero) }
241
+
242
+ it 'returns a snake_case symbol' do
243
+ expect(subject.to_sym).to eql(:value_zero)
244
+ end
245
+ end
246
+
247
+ context '#to_i' do
248
+ subject { enum.get(:value_1) }
249
+
250
+ it 'returns the Enum index' do
251
+ expect(subject.to_i).to eql(1)
252
+ end
253
+ end
254
+
255
+ context '#to_json' do
256
+ subject { enum.get(:value_1) }
257
+
258
+ it 'returns a symbol string key value' do
259
+ expect(subject.to_json).to eql('"value_1"')
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,81 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ably::Modules::EventEmitter do
4
+ let(:options) { {} }
5
+ let(:klass) do
6
+ callback_opts = options
7
+ Class.new do
8
+ include Ably::Modules::EventEmitter
9
+ configure_event_emitter callback_opts
10
+ end
11
+ end
12
+ let(:obj) { double('example') }
13
+ let(:msg) { double('message') }
14
+
15
+ subject { klass.new }
16
+
17
+ context 'event fan out' do
18
+ specify do
19
+ expect(obj).to receive(:received_message).with(msg).twice
20
+ 2.times do
21
+ subject.on(:message) { |msg| obj.received_message msg }
22
+ end
23
+ subject.trigger :message, msg
24
+ end
25
+
26
+ it 'sends only messages to matching event names' do
27
+ expect(obj).to receive(:received_message).with(msg).once
28
+ subject.on(:valid) { |msg| obj.received_message msg }
29
+ subject.trigger :valid, msg
30
+ subject.trigger :ignored, msg
31
+ subject.trigger 'valid', msg
32
+ end
33
+
34
+ context 'with coercion' do
35
+ let(:options) do
36
+ { coerce_into: Proc.new { |event| String(event) } }
37
+ end
38
+
39
+ it 'calls the provided proc to coerce the event name' do
40
+ expect(obj).to receive(:received_message).with(msg).once
41
+ subject.on('valid') { |msg| obj.received_message msg }
42
+ subject.trigger :valid, msg
43
+ end
44
+ end
45
+
46
+ context 'without coercion' do
47
+ it 'only matches event names on type matches' do
48
+ expect(obj).to_not receive(:received_message).with(msg)
49
+ subject.on('valid') { |msg| obj.received_message msg }
50
+ subject.trigger :valid, msg
51
+ end
52
+ end
53
+ end
54
+
55
+ context '#off' do
56
+ let(:callback) { Proc.new { |msg| obj.received_message msg } }
57
+
58
+ before do
59
+ subject.on(:message, &callback)
60
+ end
61
+
62
+ after do
63
+ subject.trigger :message, msg
64
+ end
65
+
66
+ it 'deletes matching callbacks' do
67
+ expect(obj).to_not receive(:received_message).with(msg)
68
+ subject.off(:message, &callback)
69
+ end
70
+
71
+ it 'deletes all callbacks if not block given' do
72
+ expect(obj).to_not receive(:received_message).with(msg)
73
+ subject.off(:message)
74
+ end
75
+
76
+ it 'continues if the block does not exist' do
77
+ expect(obj).to receive(:received_message).with(msg)
78
+ subject.off(:message) { true }
79
+ end
80
+ end
81
+ end