ably 0.1.5 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -1
  3. data/ably.gemspec +4 -3
  4. data/lib/ably.rb +6 -2
  5. data/lib/ably/auth.rb +24 -16
  6. data/lib/ably/exceptions.rb +16 -5
  7. data/lib/ably/{realtime/models → models}/error_info.rb +9 -11
  8. data/lib/ably/models/idiomatic_ruby_wrapper.rb +57 -26
  9. data/lib/ably/{realtime/models → models}/message.rb +45 -38
  10. data/lib/ably/{realtime/models → models}/nil_channel.rb +4 -4
  11. data/lib/ably/{rest/models/paged_resource.rb → models/paginated_resource.rb} +21 -10
  12. data/lib/ably/models/presence_message.rb +126 -0
  13. data/lib/ably/{realtime/models → models}/protocol_message.rb +76 -38
  14. data/lib/ably/models/token.rb +74 -0
  15. data/lib/ably/modules/channels_collection.rb +49 -0
  16. data/lib/ably/modules/conversions.rb +2 -0
  17. data/lib/ably/modules/event_emitter.rb +43 -8
  18. data/lib/ably/modules/event_machine_helpers.rb +1 -0
  19. data/lib/ably/modules/http_helpers.rb +9 -2
  20. data/lib/ably/modules/message_pack.rb +14 -0
  21. data/lib/ably/modules/model_common.rb +29 -0
  22. data/lib/ably/modules/{state.rb → state_emitter.rb} +8 -7
  23. data/lib/ably/realtime.rb +37 -7
  24. data/lib/ably/realtime/channel.rb +154 -31
  25. data/lib/ably/realtime/channels.rb +47 -0
  26. data/lib/ably/realtime/client.rb +39 -33
  27. data/lib/ably/realtime/client/incoming_message_dispatcher.rb +50 -21
  28. data/lib/ably/realtime/client/outgoing_message_dispatcher.rb +9 -11
  29. data/lib/ably/realtime/connection.rb +148 -79
  30. data/lib/ably/realtime/connection/connection_state_machine.rb +111 -0
  31. data/lib/ably/realtime/connection/websocket_transport.rb +161 -0
  32. data/lib/ably/realtime/presence.rb +270 -0
  33. data/lib/ably/rest.rb +14 -3
  34. data/lib/ably/rest/channel.rb +3 -3
  35. data/lib/ably/rest/channels.rb +26 -12
  36. data/lib/ably/rest/client.rb +42 -25
  37. data/lib/ably/rest/middleware/exceptions.rb +21 -23
  38. data/lib/ably/rest/middleware/external_exceptions.rb +8 -10
  39. data/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  40. data/lib/ably/rest/middleware/parse_json.rb +9 -2
  41. data/lib/ably/rest/middleware/parse_message_pack.rb +6 -2
  42. data/lib/ably/rest/presence.rb +4 -4
  43. data/lib/ably/version.rb +1 -1
  44. data/spec/acceptance/realtime/channel_history_spec.rb +125 -0
  45. data/spec/acceptance/realtime/channel_spec.rb +135 -63
  46. data/spec/acceptance/realtime/connection_spec.rb +86 -0
  47. data/spec/acceptance/realtime/message_spec.rb +116 -94
  48. data/spec/acceptance/realtime/presence_history_spec.rb +0 -0
  49. data/spec/acceptance/realtime/presence_spec.rb +277 -0
  50. data/spec/acceptance/rest/auth_spec.rb +351 -347
  51. data/spec/acceptance/rest/base_spec.rb +43 -26
  52. data/spec/acceptance/rest/channel_spec.rb +88 -83
  53. data/spec/acceptance/rest/channels_spec.rb +32 -28
  54. data/spec/acceptance/rest/presence_spec.rb +83 -63
  55. data/spec/acceptance/rest/stats_spec.rb +38 -37
  56. data/spec/acceptance/rest/time_spec.rb +10 -6
  57. data/spec/integration/modules/{state_spec.rb → state_emitter_spec.rb} +16 -2
  58. data/spec/spec_helper.rb +14 -0
  59. data/spec/support/api_helper.rb +4 -0
  60. data/spec/support/model_helper.rb +28 -9
  61. data/spec/support/protocol_msgbus_helper.rb +8 -1
  62. data/spec/support/test_app.rb +24 -14
  63. data/spec/unit/{realtime → models}/error_info_spec.rb +4 -4
  64. data/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +46 -9
  65. data/spec/unit/models/message_spec.rb +229 -0
  66. data/spec/unit/{rest/paged_resource_spec.rb → models/paginated_resource_spec.rb} +19 -11
  67. data/spec/unit/models/presence_message_spec.rb +230 -0
  68. data/spec/unit/models/protocol_message_spec.rb +280 -0
  69. data/spec/unit/{token_spec.rb → models/token_spec.rb} +18 -22
  70. data/spec/unit/modules/conversions_spec.rb +1 -1
  71. data/spec/unit/modules/event_emitter_spec.rb +36 -4
  72. data/spec/unit/realtime/channel_spec.rb +76 -2
  73. data/spec/unit/realtime/channels_spec.rb +50 -0
  74. data/spec/unit/realtime/client_spec.rb +31 -1
  75. data/spec/unit/realtime/connection_spec.rb +8 -15
  76. data/spec/unit/realtime/incoming_message_dispatcher_spec.rb +6 -6
  77. data/spec/unit/realtime/presence_spec.rb +100 -0
  78. data/spec/unit/rest/channels_spec.rb +48 -0
  79. metadata +72 -38
  80. data/lib/ably/realtime/models/shared.rb +0 -17
  81. data/lib/ably/rest/models/message.rb +0 -64
  82. data/lib/ably/rest/models/presence_message.rb +0 -21
  83. data/lib/ably/token.rb +0 -80
  84. data/spec/unit/realtime/message_spec.rb +0 -117
  85. data/spec/unit/realtime/protocol_message_spec.rb +0 -172
  86. data/spec/unit/rest/message_spec.rb +0 -75
@@ -0,0 +1,280 @@
1
+ require 'spec_helper'
2
+ require 'support/model_helper'
3
+
4
+ describe Ably::Models::ProtocolMessage do
5
+ include Ably::Modules::Conversions
6
+ subject { Ably::Models::ProtocolMessage }
7
+
8
+ def new_protocol_message(options)
9
+ subject.new({ action: 1 }.merge(options))
10
+ end
11
+
12
+ it_behaves_like 'a model',
13
+ with_simple_attributes: %w(id channel channel_serial connection_id),
14
+ base_model_options: { action: 1 } do
15
+
16
+ let(:model_args) { [] }
17
+ end
18
+
19
+ context 'initializer action coercion' do
20
+ it 'ignores actions that are Integers' do
21
+ protocol_message = subject.new(action: 14)
22
+ expect(protocol_message.hash[:action]).to eql(14)
23
+ end
24
+
25
+ it 'converts actions to Integers if a symbol' do
26
+ protocol_message = subject.new(action: :message)
27
+ expect(protocol_message.hash[:action]).to eql(15)
28
+ end
29
+
30
+ it 'converts actions to Integers if a ACTION' do
31
+ protocol_message = subject.new(action: Ably::Models::ProtocolMessage::ACTION.Message)
32
+ expect(protocol_message.hash[:action]).to eql(15)
33
+ end
34
+
35
+ it 'raises an argument error if nil' do
36
+ expect { subject.new({}) }.to raise_error(ArgumentError)
37
+ end
38
+ end
39
+
40
+ context 'attributes' do
41
+ let(:unique_value) { SecureRandom.hex }
42
+
43
+ context 'Java naming' do
44
+ let(:protocol_message) { new_protocol_message(channelSerial: unique_value) }
45
+
46
+ it 'converts the attribute to ruby symbol naming convention' do
47
+ expect(protocol_message.channel_serial).to eql(unique_value)
48
+ end
49
+ end
50
+
51
+ context '#action' do
52
+ let(:protocol_message) { new_protocol_message(action: 14) }
53
+
54
+ it 'returns an Enum that behaves like a symbol' do
55
+ expect(protocol_message.action).to eq(:presence)
56
+ end
57
+
58
+ it 'returns an Enum that behaves like a Numeric' do
59
+ expect(protocol_message.action).to eq(14)
60
+ end
61
+
62
+ it 'returns an Enum that behaves like a String' do
63
+ expect(protocol_message.action).to eq('Presence')
64
+ end
65
+
66
+ it 'returns an Enum that matchdes the ACTION constant' do
67
+ expect(protocol_message.action).to eql(Ably::Models::ProtocolMessage::ACTION.Presence)
68
+ end
69
+ end
70
+
71
+ context '#timestamp' do
72
+ let(:protocol_message) { new_protocol_message(timestamp: as_since_epoch(Time.now)) }
73
+ it 'retrieves attribute :timestamp' do
74
+ expect(protocol_message.timestamp).to be_a(Time)
75
+ expect(protocol_message.timestamp.to_i).to be_within(1).of(Time.now.to_i)
76
+ end
77
+ end
78
+
79
+ context '#count' do
80
+ context 'when missing' do
81
+ let(:protocol_message) { new_protocol_message({}) }
82
+ it 'is 1' do
83
+ expect(protocol_message.count).to eql(1)
84
+ end
85
+ end
86
+
87
+ context 'when non numeric' do
88
+ let(:protocol_message) { new_protocol_message(count: 'A') }
89
+ it 'is 1' do
90
+ expect(protocol_message.count).to eql(1)
91
+ end
92
+ end
93
+
94
+ context 'when greater than 1' do
95
+ let(:protocol_message) { new_protocol_message(count: '666') }
96
+ it 'is the value of count' do
97
+ expect(protocol_message.count).to eql(666)
98
+ end
99
+ end
100
+ end
101
+
102
+ context '#message_serial' do
103
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
104
+ it 'converts :msg_serial to an Integer' do
105
+ expect(protocol_message.message_serial).to be_a(Integer)
106
+ expect(protocol_message.message_serial).to eql(55)
107
+ end
108
+ end
109
+
110
+ context '#has_message_serial?' do
111
+ context 'without msg_serial' do
112
+ let(:protocol_message) { new_protocol_message({}) }
113
+
114
+ it 'returns false' do
115
+ expect(protocol_message.has_message_serial?).to eql(false)
116
+ end
117
+ end
118
+
119
+ context 'with msg_serial' do
120
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
121
+
122
+ it 'returns true' do
123
+ expect(protocol_message.has_message_serial?).to eql(true)
124
+ end
125
+ end
126
+ end
127
+
128
+ context '#connection_serial' do
129
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
130
+ it 'converts :connection_serial to an Integer' do
131
+ expect(protocol_message.connection_serial).to be_a(Integer)
132
+ expect(protocol_message.connection_serial).to eql(55)
133
+ end
134
+ end
135
+
136
+ context '#has_connection_serial?' do
137
+ context 'without connection_serial' do
138
+ let(:protocol_message) { new_protocol_message({}) }
139
+
140
+ it 'returns false' do
141
+ expect(protocol_message.has_connection_serial?).to eql(false)
142
+ end
143
+ end
144
+
145
+ context 'with connection_serial' do
146
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
147
+
148
+ it 'returns true' do
149
+ expect(protocol_message.has_connection_serial?).to eql(true)
150
+ end
151
+ end
152
+ end
153
+
154
+ context '#serial' do
155
+ context 'with underlying msg_serial' do
156
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
157
+ it 'converts :msg_serial to an Integer' do
158
+ expect(protocol_message.serial).to be_a(Integer)
159
+ expect(protocol_message.serial).to eql(55)
160
+ end
161
+ end
162
+
163
+ context 'with underlying connection_serial' do
164
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
165
+ it 'converts :connection_serial to an Integer' do
166
+ expect(protocol_message.serial).to be_a(Integer)
167
+ expect(protocol_message.serial).to eql(55)
168
+ end
169
+ end
170
+
171
+ context 'with underlying connection_serial and msg_serial' do
172
+ let(:protocol_message) { new_protocol_message(connection_serial: "99", msg_serial: "11") }
173
+ it 'prefers connection_serial and converts :connection_serial to an Integer' do
174
+ expect(protocol_message.serial).to be_a(Integer)
175
+ expect(protocol_message.serial).to eql(99)
176
+ end
177
+ end
178
+ end
179
+
180
+ context '#has_serial?' do
181
+ context 'without msg_serial or connection_serial' do
182
+ let(:protocol_message) { new_protocol_message({}) }
183
+
184
+ it 'returns false' do
185
+ expect(protocol_message.has_serial?).to eql(false)
186
+ end
187
+ end
188
+
189
+ context 'with msg_serial' do
190
+ let(:protocol_message) { new_protocol_message(msg_serial: "55") }
191
+
192
+ it 'returns true' do
193
+ expect(protocol_message.has_serial?).to eql(true)
194
+ end
195
+ end
196
+
197
+ context 'with connection_serial' do
198
+ let(:protocol_message) { new_protocol_message(connection_serial: "55") }
199
+
200
+ it 'returns true' do
201
+ expect(protocol_message.has_serial?).to eql(true)
202
+ end
203
+ end
204
+ end
205
+
206
+ context '#error' do
207
+ context 'with no error attribute' do
208
+ let(:protocol_message) { new_protocol_message(action: 1) }
209
+
210
+ it 'returns nil' do
211
+ expect(protocol_message.error).to be_nil
212
+ end
213
+ end
214
+
215
+ context 'with nil error' do
216
+ let(:protocol_message) { new_protocol_message(error: nil) }
217
+
218
+ it 'returns nil' do
219
+ expect(protocol_message.error).to be_nil
220
+ end
221
+ end
222
+
223
+ context 'with error' do
224
+ let(:protocol_message) { new_protocol_message(error: { message: 'test_error' }) }
225
+
226
+ it 'returns a valid ErrorInfo object' do
227
+ expect(protocol_message.error).to be_a(Ably::Models::ErrorInfo)
228
+ expect(protocol_message.error.message).to eql('test_error')
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ context '#to_json' do
235
+ let(:json_object) { JSON.parse(model.to_json) }
236
+ let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
237
+ let(:attached_action) { Ably::Models::ProtocolMessage::ACTION.Attached }
238
+ let(:message_action) { Ably::Models::ProtocolMessage::ACTION.Message }
239
+
240
+ context 'with valid data' do
241
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message] }) }
242
+
243
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
244
+ expect(json_object["channelSerial"]).to eql('unique')
245
+ end
246
+
247
+ it 'populates the messages' do
248
+ expect(json_object["messages"].first).to include(message)
249
+ end
250
+ end
251
+
252
+ context 'with missing msg_serial for ack message' do
253
+ let(:model) { new_protocol_message({ :action => message_action }) }
254
+
255
+ it 'it raises an exception' do
256
+ expect { model.to_json }.to raise_error TypeError, /msg_serial.*missing/
257
+ end
258
+ end
259
+
260
+ context 'is aliased by #to_s' do
261
+ let(:model) { new_protocol_message({ :action => attached_action, :channelSerial => 'unique', messages: [message], :timestamp => as_since_epoch(Time.now) }) }
262
+
263
+ specify do
264
+ expect(json_object).to eql(JSON.parse("#{model}"))
265
+ end
266
+ end
267
+ end
268
+
269
+ context '#to_msgpack' do
270
+ let(:model) { new_protocol_message({ :connectionSerial => 'unique', messages: [message] }) }
271
+ let(:message) { { 'name' => 'event', 'clientId' => 'joe', 'timestamp' => as_since_epoch(Time.now) } }
272
+ let(:packed) { model.to_msgpack }
273
+ let(:unpacked) { MessagePack.unpack(packed) }
274
+
275
+ it 'returns a unpackable msgpack object' do
276
+ expect(unpacked['connectionSerial']).to eq('unique')
277
+ expect(unpacked['messages'][0]['name']).to eq('event')
278
+ end
279
+ end
280
+ end
@@ -1,38 +1,34 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Ably::Token do
3
+ describe Ably::Models::Token do
4
+ subject { Ably::Models::Token }
5
+
6
+ it_behaves_like 'a model', with_simple_attributes: %w(id capability client_id nonce) do
7
+ let(:model_args) { [] }
8
+ end
9
+
4
10
  context 'defaults' do
5
11
  let(:one_hour) { 60 * 60 }
6
12
  let(:all_capabilities) { { "*" => ["*"] } }
7
13
 
8
14
  it 'should default TTL to 1 hour' do
9
- expect(Ably::Token::DEFAULTS[:ttl]).to eql(one_hour)
15
+ expect(Ably::Models::Token::DEFAULTS[:ttl]).to eql(one_hour)
10
16
  end
11
17
 
12
18
  it 'should default capability to all' do
13
- expect(Ably::Token::DEFAULTS[:capability]).to eql(all_capabilities)
19
+ expect(Ably::Models::Token::DEFAULTS[:capability]).to eql(all_capabilities)
14
20
  end
15
21
 
16
22
  it 'should only have defaults for :ttl and :capability' do
17
- expect(Ably::Token::DEFAULTS.keys).to contain_exactly(:ttl, :capability)
23
+ expect(Ably::Models::Token::DEFAULTS.keys).to contain_exactly(:ttl, :capability)
18
24
  end
19
25
  end
20
26
 
21
27
  context 'attributes' do
22
28
  let(:unique_value) { 'unique_value' }
23
29
 
24
- %w(id capability client_id nonce).each do |attribute|
25
- context "##{attribute}" do
26
- subject { Ably::Token.new({ attribute.to_sym => unique_value }) }
27
-
28
- it "retrieves attribute :#{attribute}" do
29
- expect(subject.public_send(attribute)).to eql(unique_value)
30
- end
31
- end
32
- end
33
-
34
30
  context '#key_id' do
35
- subject { Ably::Token.new({ key: unique_value }) }
31
+ subject { Ably::Models::Token.new({ key: unique_value }) }
36
32
  it 'retrieves attribute :key' do
37
33
  expect(subject.key_id).to eql(unique_value)
38
34
  end
@@ -41,7 +37,7 @@ describe Ably::Token do
41
37
  { :issued_at => :issued_at, :expires_at => :expires }.each do |method_name, attribute|
42
38
  let(:time) { Time.now }
43
39
  context "##{method_name}" do
44
- subject { Ably::Token.new({ attribute.to_sym => time.to_i }) }
40
+ subject { Ably::Models::Token.new({ attribute.to_sym => time.to_i }) }
45
41
 
46
42
  it "retrieves attribute :#{attribute} as Time" do
47
43
  expect(subject.public_send(method_name)).to be_a(Time)
@@ -51,10 +47,10 @@ describe Ably::Token do
51
47
  end
52
48
 
53
49
  context '#expired?' do
54
- let(:expire_time) { Time.now + Ably::Token::TOKEN_EXPIRY_BUFFER }
50
+ let(:expire_time) { Time.now + Ably::Models::Token::TOKEN_EXPIRY_BUFFER }
55
51
 
56
52
  context 'once grace period buffer has passed' do
57
- subject { Ably::Token.new(expires: expire_time - 1) }
53
+ subject { Ably::Models::Token.new(expires: expire_time - 1) }
58
54
 
59
55
  it 'is true' do
60
56
  expect(subject.expired?).to eql(true)
@@ -62,7 +58,7 @@ describe Ably::Token do
62
58
  end
63
59
 
64
60
  context 'within grace period buffer' do
65
- subject { Ably::Token.new(expires: expire_time + 1) }
61
+ subject { Ably::Models::Token.new(expires: expire_time + 1) }
66
62
 
67
63
  it 'is false' do
68
64
  expect(subject.expired?).to eql(false)
@@ -75,16 +71,16 @@ describe Ably::Token do
75
71
  let(:token_attributes) { { id: 'unique' } }
76
72
 
77
73
  it 'is true when attributes are the same' do
78
- new_token = -> { Ably::Token.new(token_attributes) }
74
+ new_token = -> { Ably::Models::Token.new(token_attributes) }
79
75
  expect(new_token[]).to eq(new_token[])
80
76
  end
81
77
 
82
78
  it 'is false when attributes are not the same' do
83
- expect(Ably::Token.new(id: 1)).to_not eq(Ably::Token.new(id: 2))
79
+ expect(Ably::Models::Token.new(id: 1)).to_not eq(Ably::Models::Token.new(id: 2))
84
80
  end
85
81
 
86
82
  it 'is false when class type differs' do
87
- expect(Ably::Token.new(id: 1)).to_not eq(nil)
83
+ expect(Ably::Models::Token.new(id: 1)).to_not eq(nil)
88
84
  end
89
85
  end
90
86
  end
@@ -49,7 +49,7 @@ describe Ably::Modules::Conversions do
49
49
  let(:seconds) { Time.new.to_f }
50
50
 
51
51
  it 'converts to Time from milliseconds by default' do
52
- expect(subject.as_time_from_epoch(millisecond).to_f).to be_within(0.001).of(time.to_f)
52
+ expect(subject.as_time_from_epoch(millisecond).to_f).to be_within(0.01).of(time.to_f)
53
53
  end
54
54
 
55
55
  it 'converts to Time from seconds' do
@@ -16,16 +16,18 @@ describe Ably::Modules::EventEmitter do
16
16
 
17
17
  context 'event fan out' do
18
18
  specify do
19
- expect(obj).to receive(:received_message).with(msg).twice
20
19
  2.times do
21
20
  subject.on(:message) { |msg| obj.received_message msg }
22
21
  end
22
+
23
+ expect(obj).to receive(:received_message).with(msg).twice
23
24
  subject.trigger :message, msg
24
25
  end
25
26
 
26
27
  it 'sends only messages to matching event names' do
27
- expect(obj).to receive(:received_message).with(msg).once
28
28
  subject.on(:valid) { |msg| obj.received_message msg }
29
+
30
+ expect(obj).to receive(:received_message).with(msg).once
29
31
  subject.trigger :valid, msg
30
32
  subject.trigger :ignored, msg
31
33
  subject.trigger 'valid', msg
@@ -37,19 +39,49 @@ describe Ably::Modules::EventEmitter do
37
39
  end
38
40
 
39
41
  it 'calls the provided proc to coerce the event name' do
40
- expect(obj).to receive(:received_message).with(msg).once
41
42
  subject.on('valid') { |msg| obj.received_message msg }
43
+
44
+ expect(obj).to receive(:received_message).with(msg).once
42
45
  subject.trigger :valid, msg
43
46
  end
44
47
  end
45
48
 
46
49
  context 'without coercion' do
47
50
  it 'only matches event names on type matches' do
48
- expect(obj).to_not receive(:received_message).with(msg)
49
51
  subject.on('valid') { |msg| obj.received_message msg }
52
+
53
+ expect(obj).to_not receive(:received_message).with(msg)
50
54
  subject.trigger :valid, msg
51
55
  end
52
56
  end
57
+
58
+ context 'subscribe to multiple events' do
59
+ it 'with the same block' do
60
+ subject.on(:click, :hover) { |msg| obj.received_message msg }
61
+
62
+ expect(obj).to receive(:received_message).with(msg).twice
63
+
64
+ subject.trigger :click, msg
65
+ subject.trigger :hover, msg
66
+ end
67
+ end
68
+ end
69
+
70
+ context '#once' do
71
+ it 'calls the block the first time an event is emitted only' do
72
+ block_called = 0
73
+ subject.once('event') { block_called += 1 }
74
+ 3.times { subject.trigger 'event', 'data' }
75
+ expect(block_called).to eql(1)
76
+ end
77
+
78
+ it 'does not remove other blocks after it is called' do
79
+ block_called = 0
80
+ subject.once('event') { block_called += 1 }
81
+ subject.on('event') { block_called += 1 }
82
+ 3.times { subject.trigger 'event', 'data' }
83
+ expect(block_called).to eql(4)
84
+ end
53
85
  end
54
86
 
55
87
  context '#off' do