ably-rest 0.7.1 → 0.7.3

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 (148) hide show
  1. checksums.yaml +13 -5
  2. data/.gitmodules +1 -1
  3. data/.rspec +1 -0
  4. data/.travis.yml +7 -3
  5. data/SPEC.md +495 -419
  6. data/ably-rest.gemspec +19 -5
  7. data/lib/ably-rest.rb +9 -1
  8. data/lib/submodules/ably-ruby/.gitignore +6 -0
  9. data/lib/submodules/ably-ruby/.rspec +1 -0
  10. data/lib/submodules/ably-ruby/.ruby-version.old +1 -0
  11. data/lib/submodules/ably-ruby/.travis.yml +10 -0
  12. data/lib/submodules/ably-ruby/Gemfile +4 -0
  13. data/lib/submodules/ably-ruby/LICENSE.txt +22 -0
  14. data/lib/submodules/ably-ruby/README.md +122 -0
  15. data/lib/submodules/ably-ruby/Rakefile +34 -0
  16. data/lib/submodules/ably-ruby/SPEC.md +1794 -0
  17. data/lib/submodules/ably-ruby/ably.gemspec +36 -0
  18. data/lib/submodules/ably-ruby/lib/ably.rb +12 -0
  19. data/lib/submodules/ably-ruby/lib/ably/auth.rb +438 -0
  20. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +69 -0
  21. data/lib/submodules/ably-ruby/lib/ably/logger.rb +102 -0
  22. data/lib/submodules/ably-ruby/lib/ably/models/error_info.rb +37 -0
  23. data/lib/submodules/ably-ruby/lib/ably/models/idiomatic_ruby_wrapper.rb +223 -0
  24. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +132 -0
  25. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +108 -0
  26. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +40 -0
  27. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/cipher.rb +83 -0
  28. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/json.rb +34 -0
  29. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/utf8.rb +26 -0
  30. data/lib/submodules/ably-ruby/lib/ably/models/nil_logger.rb +20 -0
  31. data/lib/submodules/ably-ruby/lib/ably/models/paginated_resource.rb +173 -0
  32. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +147 -0
  33. data/lib/submodules/ably-ruby/lib/ably/models/protocol_message.rb +210 -0
  34. data/lib/submodules/ably-ruby/lib/ably/models/stat.rb +161 -0
  35. data/lib/submodules/ably-ruby/lib/ably/models/token.rb +74 -0
  36. data/lib/submodules/ably-ruby/lib/ably/modules/ably.rb +15 -0
  37. data/lib/submodules/ably-ruby/lib/ably/modules/async_wrapper.rb +62 -0
  38. data/lib/submodules/ably-ruby/lib/ably/modules/channels_collection.rb +69 -0
  39. data/lib/submodules/ably-ruby/lib/ably/modules/conversions.rb +100 -0
  40. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +69 -0
  41. data/lib/submodules/ably-ruby/lib/ably/modules/enum.rb +202 -0
  42. data/lib/submodules/ably-ruby/lib/ably/modules/event_emitter.rb +128 -0
  43. data/lib/submodules/ably-ruby/lib/ably/modules/event_machine_helpers.rb +26 -0
  44. data/lib/submodules/ably-ruby/lib/ably/modules/http_helpers.rb +41 -0
  45. data/lib/submodules/ably-ruby/lib/ably/modules/message_pack.rb +14 -0
  46. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +41 -0
  47. data/lib/submodules/ably-ruby/lib/ably/modules/state_emitter.rb +153 -0
  48. data/lib/submodules/ably-ruby/lib/ably/modules/state_machine.rb +57 -0
  49. data/lib/submodules/ably-ruby/lib/ably/modules/statesman_monkey_patch.rb +33 -0
  50. data/lib/submodules/ably-ruby/lib/ably/modules/uses_state_machine.rb +74 -0
  51. data/lib/submodules/ably-ruby/lib/ably/realtime.rb +64 -0
  52. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +298 -0
  53. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_manager.rb +92 -0
  54. data/lib/submodules/ably-ruby/lib/ably/realtime/channel/channel_state_machine.rb +69 -0
  55. data/lib/submodules/ably-ruby/lib/ably/realtime/channels.rb +50 -0
  56. data/lib/submodules/ably-ruby/lib/ably/realtime/client.rb +184 -0
  57. data/lib/submodules/ably-ruby/lib/ably/realtime/client/incoming_message_dispatcher.rb +184 -0
  58. data/lib/submodules/ably-ruby/lib/ably/realtime/client/outgoing_message_dispatcher.rb +70 -0
  59. data/lib/submodules/ably-ruby/lib/ably/realtime/connection.rb +445 -0
  60. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_manager.rb +368 -0
  61. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/connection_state_machine.rb +91 -0
  62. data/lib/submodules/ably-ruby/lib/ably/realtime/connection/websocket_transport.rb +188 -0
  63. data/lib/submodules/ably-ruby/lib/ably/realtime/models/nil_channel.rb +30 -0
  64. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +564 -0
  65. data/lib/submodules/ably-ruby/lib/ably/rest.rb +43 -0
  66. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +104 -0
  67. data/lib/submodules/ably-ruby/lib/ably/rest/channels.rb +44 -0
  68. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +396 -0
  69. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/encoder.rb +49 -0
  70. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/exceptions.rb +41 -0
  71. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/external_exceptions.rb +24 -0
  72. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/fail_if_unsupported_mime_type.rb +17 -0
  73. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/logger.rb +58 -0
  74. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_json.rb +27 -0
  75. data/lib/submodules/ably-ruby/lib/ably/rest/middleware/parse_message_pack.rb +27 -0
  76. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +92 -0
  77. data/lib/submodules/ably-ruby/lib/ably/util/crypto.rb +105 -0
  78. data/lib/submodules/ably-ruby/lib/ably/util/pub_sub.rb +43 -0
  79. data/lib/submodules/ably-ruby/lib/ably/version.rb +3 -0
  80. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_history_spec.rb +154 -0
  81. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +558 -0
  82. data/lib/submodules/ably-ruby/spec/acceptance/realtime/client_spec.rb +119 -0
  83. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_failures_spec.rb +575 -0
  84. data/lib/submodules/ably-ruby/spec/acceptance/realtime/connection_spec.rb +785 -0
  85. data/lib/submodules/ably-ruby/spec/acceptance/realtime/message_spec.rb +457 -0
  86. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_history_spec.rb +55 -0
  87. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +1001 -0
  88. data/lib/submodules/ably-ruby/spec/acceptance/realtime/stats_spec.rb +23 -0
  89. data/lib/submodules/ably-ruby/spec/acceptance/realtime/time_spec.rb +27 -0
  90. data/lib/submodules/ably-ruby/spec/acceptance/rest/auth_spec.rb +564 -0
  91. data/lib/submodules/ably-ruby/spec/acceptance/rest/base_spec.rb +165 -0
  92. data/lib/submodules/ably-ruby/spec/acceptance/rest/channel_spec.rb +134 -0
  93. data/lib/submodules/ably-ruby/spec/acceptance/rest/channels_spec.rb +41 -0
  94. data/lib/submodules/ably-ruby/spec/acceptance/rest/client_spec.rb +273 -0
  95. data/lib/submodules/ably-ruby/spec/acceptance/rest/encoders_spec.rb +185 -0
  96. data/lib/submodules/ably-ruby/spec/acceptance/rest/message_spec.rb +247 -0
  97. data/lib/submodules/ably-ruby/spec/acceptance/rest/presence_spec.rb +292 -0
  98. data/lib/submodules/ably-ruby/spec/acceptance/rest/stats_spec.rb +172 -0
  99. data/lib/submodules/ably-ruby/spec/acceptance/rest/time_spec.rb +15 -0
  100. data/lib/submodules/ably-ruby/spec/resources/crypto-data-128.json +56 -0
  101. data/lib/submodules/ably-ruby/spec/resources/crypto-data-256.json +56 -0
  102. data/lib/submodules/ably-ruby/spec/rspec_config.rb +57 -0
  103. data/lib/submodules/ably-ruby/spec/shared/client_initializer_behaviour.rb +212 -0
  104. data/lib/submodules/ably-ruby/spec/shared/model_behaviour.rb +86 -0
  105. data/lib/submodules/ably-ruby/spec/shared/protocol_msgbus_behaviour.rb +36 -0
  106. data/lib/submodules/ably-ruby/spec/spec_helper.rb +20 -0
  107. data/lib/submodules/ably-ruby/spec/support/api_helper.rb +60 -0
  108. data/lib/submodules/ably-ruby/spec/support/event_machine_helper.rb +104 -0
  109. data/lib/submodules/ably-ruby/spec/support/markdown_spec_formatter.rb +118 -0
  110. data/lib/submodules/ably-ruby/spec/support/private_api_formatter.rb +36 -0
  111. data/lib/submodules/ably-ruby/spec/support/protocol_helper.rb +32 -0
  112. data/lib/submodules/ably-ruby/spec/support/random_helper.rb +15 -0
  113. data/lib/submodules/ably-ruby/spec/support/rest_testapp_before_retry.rb +15 -0
  114. data/lib/submodules/ably-ruby/spec/support/test_app.rb +113 -0
  115. data/lib/submodules/ably-ruby/spec/unit/auth_spec.rb +68 -0
  116. data/lib/submodules/ably-ruby/spec/unit/logger_spec.rb +146 -0
  117. data/lib/submodules/ably-ruby/spec/unit/models/error_info_spec.rb +18 -0
  118. data/lib/submodules/ably-ruby/spec/unit/models/idiomatic_ruby_wrapper_spec.rb +349 -0
  119. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +181 -0
  120. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/cipher_spec.rb +260 -0
  121. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/json_spec.rb +135 -0
  122. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/utf8_spec.rb +56 -0
  123. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +389 -0
  124. data/lib/submodules/ably-ruby/spec/unit/models/paginated_resource_spec.rb +288 -0
  125. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +386 -0
  126. data/lib/submodules/ably-ruby/spec/unit/models/protocol_message_spec.rb +315 -0
  127. data/lib/submodules/ably-ruby/spec/unit/models/stat_spec.rb +113 -0
  128. data/lib/submodules/ably-ruby/spec/unit/models/token_spec.rb +86 -0
  129. data/lib/submodules/ably-ruby/spec/unit/modules/async_wrapper_spec.rb +124 -0
  130. data/lib/submodules/ably-ruby/spec/unit/modules/conversions_spec.rb +72 -0
  131. data/lib/submodules/ably-ruby/spec/unit/modules/enum_spec.rb +272 -0
  132. data/lib/submodules/ably-ruby/spec/unit/modules/event_emitter_spec.rb +184 -0
  133. data/lib/submodules/ably-ruby/spec/unit/modules/state_emitter_spec.rb +283 -0
  134. data/lib/submodules/ably-ruby/spec/unit/realtime/channel_spec.rb +206 -0
  135. data/lib/submodules/ably-ruby/spec/unit/realtime/channels_spec.rb +81 -0
  136. data/lib/submodules/ably-ruby/spec/unit/realtime/client_spec.rb +30 -0
  137. data/lib/submodules/ably-ruby/spec/unit/realtime/connection_spec.rb +33 -0
  138. data/lib/submodules/ably-ruby/spec/unit/realtime/incoming_message_dispatcher_spec.rb +36 -0
  139. data/lib/submodules/ably-ruby/spec/unit/realtime/presence_spec.rb +111 -0
  140. data/lib/submodules/ably-ruby/spec/unit/realtime/realtime_spec.rb +9 -0
  141. data/lib/submodules/ably-ruby/spec/unit/realtime/websocket_transport_spec.rb +25 -0
  142. data/lib/submodules/ably-ruby/spec/unit/rest/channel_spec.rb +109 -0
  143. data/lib/submodules/ably-ruby/spec/unit/rest/channels_spec.rb +79 -0
  144. data/lib/submodules/ably-ruby/spec/unit/rest/client_spec.rb +53 -0
  145. data/lib/submodules/ably-ruby/spec/unit/rest/rest_spec.rb +10 -0
  146. data/lib/submodules/ably-ruby/spec/unit/util/crypto_spec.rb +87 -0
  147. data/lib/submodules/ably-ruby/spec/unit/util/pub_sub_spec.rb +86 -0
  148. metadata +182 -27
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ require 'ably/models/message_encoders/utf8'
4
+
5
+ describe Ably::Models::MessageEncoders::Utf8 do
6
+ let(:string_ascii) { 'string'.encode(Encoding::ASCII_8BIT) }
7
+ let(:string_utf8) { 'string'.encode(Encoding::UTF_8) }
8
+
9
+ let(:client) { instance_double('Ably::Realtime::Client') }
10
+
11
+ subject { Ably::Models::MessageEncoders::Utf8.new(client) }
12
+
13
+ context '#decode' do
14
+ before do
15
+ subject.decode message, {}
16
+ end
17
+
18
+ context 'message with utf8 payload' do
19
+ let(:message) { { data: string_ascii, encoding: 'utf-8' } }
20
+
21
+ it 'sets the encoding' do
22
+ expect(message[:data]).to eq(string_utf8)
23
+ expect(message[:data].encoding).to eql(Encoding::UTF_8)
24
+ end
25
+
26
+ it 'strips the encoding' do
27
+ expect(message[:encoding]).to be_nil
28
+ end
29
+ end
30
+
31
+ context 'message with utf8 payload before other payloads' do
32
+ let(:message) { { data: string_utf8, encoding: 'json/utf-8' } }
33
+
34
+ it 'sets the encoding' do
35
+ expect(message[:data]).to eql(string_utf8)
36
+ expect(message[:data].encoding).to eql(Encoding::UTF_8)
37
+ end
38
+
39
+ it 'strips the encoding' do
40
+ expect(message[:encoding]).to eql('json')
41
+ end
42
+ end
43
+
44
+ context 'message with another payload' do
45
+ let(:message) { { data: string_ascii, encoding: 'json' } }
46
+
47
+ it 'leaves the message data intact' do
48
+ expect(message[:data]).to eql(string_ascii)
49
+ end
50
+
51
+ it 'leaves the encoding intact' do
52
+ expect(message[:encoding]).to eql('json')
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,389 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'shared/model_behaviour'
4
+ require 'base64'
5
+ require 'msgpack'
6
+
7
+ describe Ably::Models::Message do
8
+ include Ably::Modules::Conversions
9
+
10
+ subject { Ably::Models::Message }
11
+ let(:protocol_message_timestamp) { as_since_epoch(Time.now) }
12
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new(action: 1, timestamp: protocol_message_timestamp) }
13
+
14
+ it_behaves_like 'a model', with_simple_attributes: %w(name client_id data encoding) do
15
+ let(:model_args) { [protocol_message] }
16
+ end
17
+
18
+ context '#timestamp' do
19
+ let(:model) { subject.new({}, protocol_message) }
20
+
21
+ it 'retrieves attribute :timestamp as Time object from ProtocolMessage' do
22
+ expect(model.timestamp).to be_a(Time)
23
+ expect(model.timestamp.to_i).to be_within(1).of(Time.now.to_i)
24
+ end
25
+ end
26
+
27
+ context '#connection_id attribute' do
28
+ let(:protocol_connection_id) { random_str }
29
+ let(:protocol_message) { Ably::Models::ProtocolMessage.new('connectionId' => protocol_connection_id, action: 1, timestamp: protocol_message_timestamp) }
30
+ let(:model_connection_id) { random_str }
31
+
32
+ context 'when this model has a connectionId attribute' do
33
+ context 'but no protocol message' do
34
+ let(:model) { subject.new('connectionId' => model_connection_id ) }
35
+
36
+ it 'uses the model value' do
37
+ expect(model.connection_id).to eql(model_connection_id)
38
+ end
39
+ end
40
+
41
+ context 'with a protocol message with a different connectionId' do
42
+ let(:model) { subject.new({ 'connectionId' => model_connection_id }, protocol_message) }
43
+
44
+ it 'uses the model value' do
45
+ expect(model.connection_id).to eql(model_connection_id)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'when this model has no connectionId attribute' do
51
+ context 'and no protocol message' do
52
+ let(:model) { subject.new({ }) }
53
+
54
+ it 'uses the model value' do
55
+ expect(model.connection_id).to be_nil
56
+ end
57
+ end
58
+
59
+ context 'with a protocol message with a connectionId' do
60
+ let(:model) { subject.new({ }, protocol_message) }
61
+
62
+ it 'uses the model value' do
63
+ expect(model.connection_id).to eql(protocol_connection_id)
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ context 'Java naming', :api_private do
70
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
71
+
72
+ it 'converts the attribute to ruby symbol naming convention' do
73
+ expect(model.client_id).to eql('joe')
74
+ end
75
+ end
76
+
77
+ context 'initialized with' do
78
+ %w(name client_id encoding).each do |attribute|
79
+ context ":#{attribute}" do
80
+ let(:encoded_value) { value.encode(encoding) }
81
+ let(:value) { random_str }
82
+ let(:options) { { attribute.to_sym => encoded_value } }
83
+ let(:model) { subject.new(options, protocol_message) }
84
+ let(:model_attribute) { model.public_send(attribute) }
85
+
86
+ context 'as UTF_8 string' do
87
+ let(:encoding) { Encoding::UTF_8 }
88
+
89
+ it 'is permitted' do
90
+ expect(model_attribute).to eql(encoded_value)
91
+ end
92
+
93
+ it 'remains as UTF-8' do
94
+ expect(model_attribute.encoding).to eql(encoding)
95
+ end
96
+ end
97
+
98
+ context 'as SHIFT_JIS string' do
99
+ let(:encoding) { Encoding::SHIFT_JIS }
100
+
101
+ it 'gets converted to UTF-8' do
102
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
103
+ end
104
+
105
+ it 'is compatible with original encoding' do
106
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
107
+ end
108
+ end
109
+
110
+ context 'as ASCII_8BIT string' do
111
+ let(:encoding) { Encoding::ASCII_8BIT }
112
+
113
+ it 'gets converted to UTF-8' do
114
+ expect(model_attribute.encoding).to eql(Encoding::UTF_8)
115
+ end
116
+
117
+ it 'is compatible with original encoding' do
118
+ expect(model_attribute.encode(encoding)).to eql(encoded_value)
119
+ end
120
+ end
121
+
122
+ context 'as Integer' do
123
+ let(:encoded_value) { 1 }
124
+
125
+ it 'raises an argument error' do
126
+ expect { model_attribute }.to raise_error ArgumentError, /must be a String/
127
+ end
128
+ end
129
+
130
+ context 'as Nil' do
131
+ let(:encoded_value) { nil }
132
+
133
+ it 'is permitted' do
134
+ expect(model_attribute).to be_nil
135
+ end
136
+ end
137
+ end
138
+ end
139
+ end
140
+
141
+ context '#to_json', :api_private do
142
+ let(:json_object) { JSON.parse(model.to_json) }
143
+
144
+ context 'with valid data' do
145
+ let(:model) { subject.new({ name: 'test', clientId: 'joe' }, protocol_message) }
146
+
147
+ it 'converts the attribute back to Java mixedCase notation using string keys' do
148
+ expect(json_object["clientId"]).to eql('joe')
149
+ end
150
+ end
151
+
152
+ context 'with invalid data' do
153
+ let(:model) { subject.new({ clientId: 'joe' }, protocol_message) }
154
+
155
+ it 'raises an exception' do
156
+ expect { model.to_json }.to raise_error RuntimeError, /cannot generate a valid Hash/
157
+ end
158
+ end
159
+
160
+ context 'with binary data' do
161
+ let(:data) { MessagePack.pack(random_str(32)) }
162
+ let(:model) { subject.new({ name: 'test', data: data }, protocol_message) }
163
+
164
+ it 'encodes as Base64 so that it can be converted to UTF-8 automatically by JSON#dump' do
165
+ expect(json_object["data"]).to eql(::Base64.encode64(data))
166
+ end
167
+
168
+ it 'adds Base64 encoding' do
169
+ expect(json_object["encoding"]).to eql('base64')
170
+ end
171
+ end
172
+ end
173
+
174
+ context 'from REST request with embedded fields', :api_private do
175
+ let(:id) { random_str }
176
+ let(:protocol_message_id) { random_str }
177
+ let(:message_time) { Time.now + 60 }
178
+ let(:message_timestamp) { as_since_epoch(message_time) }
179
+ let(:protocol_time) { Time.now }
180
+ let(:protocol_timestamp) { as_since_epoch(protocol_time) }
181
+
182
+ let(:protocol_message) do
183
+ Ably::Models::ProtocolMessage.new({
184
+ action: :message,
185
+ timestamp: protocol_timestamp,
186
+ id: protocol_message_id
187
+ })
188
+ end
189
+
190
+ context 'with protocol message' do
191
+ let(:model) { subject.new({ id: id, timestamp: message_timestamp }, protocol_message) }
192
+
193
+ specify '#id prefers embedded ID' do
194
+ expect(model.id).to eql(id)
195
+ end
196
+
197
+ specify '#timestamp prefers embedded timestamp' do
198
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
199
+ end
200
+ end
201
+
202
+ context 'without protocol message' do
203
+ let(:model) { subject.new(id: id, timestamp: message_timestamp) }
204
+
205
+ specify '#id uses embedded ID' do
206
+ expect(model.id).to eql(id)
207
+ end
208
+
209
+ specify '#timestamp uses embedded timestamp' do
210
+ expect(model.timestamp.to_i).to be_within(1).of(message_time.to_i)
211
+ end
212
+ end
213
+ end
214
+
215
+ context 'part of ProtocolMessage', :api_private do
216
+ let(:ably_time) { Time.now + 5 }
217
+ let(:message_serial) { random_int_str(1_000_000) }
218
+ let(:connection_id) { random_str }
219
+
220
+ let(:message_0_payload) do
221
+ {
222
+ 'string_key' => 'string_value',
223
+ 1 => 2,
224
+ true => false
225
+ }
226
+ end
227
+
228
+ let(:message_0_json) do
229
+ {
230
+ name: 'zero',
231
+ data: message_0_payload
232
+ }
233
+ end
234
+
235
+ let(:message_1_json) do
236
+ {
237
+ name: 'one',
238
+ data: 'simple string'
239
+ }
240
+ end
241
+
242
+ let(:protocol_message_id) { random_str }
243
+ let(:protocol_message) do
244
+ Ably::Models::ProtocolMessage.new({
245
+ action: :message,
246
+ timestamp: ably_time.to_i,
247
+ msg_serial: message_serial,
248
+ id: protocol_message_id,
249
+ messages: [
250
+ message_0_json, message_1_json
251
+ ]
252
+ })
253
+ end
254
+
255
+ let(:message_0) { protocol_message.messages.first }
256
+ let(:message_1) { protocol_message.messages.last }
257
+
258
+ it 'should generate a message ID from the index, serial and connection id' do
259
+ expect(message_0.id).to eql("#{protocol_message_id}:0")
260
+ expect(message_1.id).to eql("#{protocol_message_id}:1")
261
+ end
262
+
263
+ it 'should not modify the data payload' do
264
+ expect(message_0.data['string_key']).to eql('string_value')
265
+ expect(message_0.data[1]).to eql(2)
266
+ expect(message_0.data[true]).to eql(false)
267
+ expect(message_0.data).to eql(message_0_payload)
268
+
269
+ expect(message_1.data).to eql('simple string')
270
+ end
271
+
272
+ it 'should not allow changes to the payload' do
273
+ expect { message_0.data["test"] = true }.to raise_error RuntimeError, /can't modify frozen.*Hash/
274
+ end
275
+
276
+ context 'with identical message objects' do
277
+ let(:protocol_message) do
278
+ Ably::Models::ProtocolMessage.new({
279
+ action: :message,
280
+ timestamp: ably_time.to_i,
281
+ msg_serial: message_serial,
282
+ id: protocol_message_id,
283
+ messages: [
284
+ message_0_json, message_0_json, message_0_json
285
+ ]
286
+ })
287
+ end
288
+
289
+ it 'provide a unique ID:index' do
290
+ expect(protocol_message.messages.map(&:id).uniq.count).to eql(3)
291
+ end
292
+
293
+ it 'recognises the index based on the object ID as opposed to message payload' do
294
+ expect(protocol_message.messages.first.id).to match(/0$/)
295
+ expect(protocol_message.messages.last.id).to match(/2$/)
296
+ end
297
+ end
298
+ end
299
+
300
+ context 'Message conversion method', :api_private do
301
+ let(:json) { { name: 'test', data: 'conversion' } }
302
+
303
+ context 'with JSON' do
304
+ context 'without ProtocolMessage' do
305
+ subject { Ably::Models.Message(json) }
306
+
307
+ it 'returns a Message object' do
308
+ expect(subject).to be_a(Ably::Models::Message)
309
+ end
310
+
311
+ it 'initializes with the JSON' do
312
+ expect(subject.name).to eql('test')
313
+ end
314
+
315
+ it 'raises an exception when accessing ProtocolMessage' do
316
+ expect { subject.protocol_message }.to raise_error RuntimeError
317
+ end
318
+
319
+ it 'has no ProtocolMessage' do
320
+ expect(subject.assigned_to_protocol_message?).to eql(false)
321
+ end
322
+ end
323
+
324
+ context 'with ProtocolMessage' do
325
+ subject { Ably::Models.Message(json, protocol_message) }
326
+
327
+ it 'returns a Message object' do
328
+ expect(subject).to be_a(Ably::Models::Message)
329
+ end
330
+
331
+ it 'initializes with the JSON' do
332
+ expect(subject.name).to eql('test')
333
+ end
334
+
335
+ it 'provides access to ProtocolMessage' do
336
+ expect(subject.protocol_message).to eql(protocol_message)
337
+ end
338
+
339
+ it 'has a ProtocolMessage' do
340
+ expect(subject.assigned_to_protocol_message?).to eql(true)
341
+ end
342
+ end
343
+ end
344
+
345
+ context 'with another Message' do
346
+ let(:message) { Ably::Models::Message.new(json) }
347
+
348
+ context 'without ProtocolMessage' do
349
+ subject { Ably::Models.Message(message) }
350
+
351
+ it 'returns a Message object' do
352
+ expect(subject).to be_a(Ably::Models::Message)
353
+ end
354
+
355
+ it 'initializes with the JSON' do
356
+ expect(subject.name).to eql('test')
357
+ end
358
+
359
+ it 'raises an exception when accessing ProtocolMessage' do
360
+ expect { subject.protocol_message }.to raise_error RuntimeError
361
+ end
362
+
363
+ it 'has no ProtocolMessage' do
364
+ expect(subject.assigned_to_protocol_message?).to eql(false)
365
+ end
366
+ end
367
+
368
+ context 'with ProtocolMessage' do
369
+ subject { Ably::Models.Message(message, protocol_message) }
370
+
371
+ it 'returns a Message object' do
372
+ expect(subject).to be_a(Ably::Models::Message)
373
+ end
374
+
375
+ it 'initializes with the JSON' do
376
+ expect(subject.name).to eql('test')
377
+ end
378
+
379
+ it 'provides access to ProtocolMessage' do
380
+ expect(subject.protocol_message).to eql(protocol_message)
381
+ end
382
+
383
+ it 'has a ProtocolMessage' do
384
+ expect(subject.assigned_to_protocol_message?).to eql(true)
385
+ end
386
+ end
387
+ end
388
+ end
389
+ end
@@ -0,0 +1,288 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+
4
+ describe Ably::Models::PaginatedResource do
5
+ let(:paginated_resource_class) { Ably::Models::PaginatedResource }
6
+ let(:headers) { Hash.new }
7
+ let(:client) do
8
+ instance_double('Ably::Rest::Client').tap do |client|
9
+ allow(client).to receive(:get).and_return(http_response)
10
+ end
11
+ end
12
+ let(:body) do
13
+ [
14
+ { id: 0 },
15
+ { id: 1 }
16
+ ]
17
+ end
18
+ let(:http_response) do
19
+ instance_double('Faraday::Response', {
20
+ body: body,
21
+ headers: headers
22
+ })
23
+ end
24
+ let(:base_url) { 'http://rest.ably.io/channels/channel_name' }
25
+ let(:full_url) { "#{base_url}/whatever?param=exists" }
26
+ let(:paginated_resource_options) { Hash.new }
27
+ let(:first_paged_request) { paginated_resource_class.new(http_response, full_url, client, paginated_resource_options) }
28
+ subject { first_paged_request }
29
+
30
+ it 'returns correct length from body' do
31
+ expect(subject.length).to eql(body.length)
32
+ end
33
+
34
+ it 'supports alias methods for length' do
35
+ expect(subject.count).to eql(subject.length)
36
+ expect(subject.size).to eql(subject.length)
37
+ end
38
+
39
+ it 'is Enumerable' do
40
+ expect(subject).to be_kind_of(Enumerable)
41
+ end
42
+
43
+ it 'is iterable' do
44
+ expect(subject.map { |d| d }).to eql(body)
45
+ end
46
+
47
+ context '#each' do
48
+ it 'returns an enumerator' do
49
+ expect(subject.each).to be_a(Enumerator)
50
+ end
51
+
52
+ it 'yields each item' do
53
+ items = []
54
+ subject.each do |item|
55
+ items << item
56
+ end
57
+ expect(items).to eq(body)
58
+ end
59
+ end
60
+
61
+ it 'provides [] accessor method' do
62
+ expect(subject[0][:id]).to eql(body[0][:id])
63
+ expect(subject[1][:id]).to eql(body[1][:id])
64
+ expect(subject[2]).to be_nil
65
+ end
66
+
67
+ specify '#first gets the first item in page' do
68
+ expect(subject.first[:id]).to eql(body[0][:id])
69
+ end
70
+
71
+ specify '#last gets the last item in page' do
72
+ expect(subject.last[:id]).to eql(body[1][:id])
73
+ end
74
+
75
+ context 'with coercion', :api_private do
76
+ let(:paginated_resource_options) { { coerce_into: 'OpenStruct' } }
77
+
78
+ it 'returns coerced objects' do
79
+ expect(subject.first).to be_a(OpenStruct)
80
+ expect(subject.first.id).to eql(body.first[:id])
81
+ end
82
+ end
83
+
84
+ context 'paged transformations', :api_private do
85
+ let(:headers) do
86
+ {
87
+ 'link' => [
88
+ '<./history?index=1>; rel="next"'
89
+ ].join(', ')
90
+ }
91
+ end
92
+ let(:paged_client) do
93
+ instance_double('Ably::Rest::Client').tap do |client|
94
+ allow(client).to receive(:get).and_return(http_response_page2)
95
+ end
96
+ end
97
+ let(:body_page2) do
98
+ [
99
+ { id: 2 },
100
+ { id: 3 }
101
+ ]
102
+ end
103
+ let(:http_response_page2) do
104
+ instance_double('Faraday::Response', {
105
+ body: body_page2,
106
+ headers: headers
107
+ })
108
+ end
109
+
110
+ context 'with each block' do
111
+ subject do
112
+ paginated_resource_class.new(http_response, full_url, paged_client, paginated_resource_options) do |resource|
113
+ resource[:added_attribute_from_block] = "id:#{resource[:id]}"
114
+ resource
115
+ end
116
+ end
117
+
118
+ it 'calls the block for each resource after retrieving the resources' do
119
+ expect(subject[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
120
+ end
121
+
122
+ it 'calls the block for each resource on second page after retrieving the resources' do
123
+ page_1_first_id = subject[0][:id]
124
+ next_page = subject.next_page
125
+
126
+ expect(next_page[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
127
+ expect(next_page[0][:id]).to_not eql(page_1_first_id)
128
+ end
129
+ end
130
+
131
+ if defined?(Ably::Realtime)
132
+ context 'with option async_blocking_operations: true' do
133
+ include RSpec::EventMachine
134
+
135
+ subject do
136
+ paginated_resource_class.new(http_response, full_url, paged_client, async_blocking_operations: true)
137
+ end
138
+
139
+ context '#next_page' do
140
+ it 'returns a deferrable object' do
141
+ run_reactor do
142
+ expect(subject.next_page).to be_a(EventMachine::Deferrable)
143
+ stop_reactor
144
+ end
145
+ end
146
+
147
+ it 'allows a success callback block to be added' do
148
+ run_reactor do
149
+ subject.next_page do |paginated_resource|
150
+ expect(paginated_resource).to be_a(Ably::Models::PaginatedResource)
151
+ stop_reactor
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ context '#first_page' do
158
+ it 'calls the errback callback when first page headers are missing' do
159
+ run_reactor do
160
+ subject.next_page do |paginated_resource|
161
+ deferrable = subject.first_page
162
+ deferrable.errback do |error|
163
+ expect(error).to be_a(Ably::Exceptions::InvalidPageError)
164
+ stop_reactor
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end
173
+
174
+ context 'with non paged http response' do
175
+ it 'is the first page' do
176
+ expect(subject).to be_first_page
177
+ end
178
+
179
+ it 'is the last page' do
180
+ expect(subject).to be_last_page
181
+ end
182
+
183
+ it 'does not support pagination' do
184
+ expect(subject.supports_pagination?).to_not eql(true)
185
+ end
186
+
187
+ it 'raises an exception when accessing next page' do
188
+ expect { subject.next_page }.to raise_error Ably::Exceptions::InvalidPageError, /Paging header link next/
189
+ end
190
+
191
+ it 'raises an exception when accessing first page' do
192
+ expect { subject.first_page }.to raise_error Ably::Exceptions::InvalidPageError, /Paging header link first/
193
+ end
194
+ end
195
+
196
+ context 'with paged http response' do
197
+ let(:base_url) { 'http://rest.ably.io/channels/channel_name' }
198
+ let(:full_url) { "#{base_url}/messages" }
199
+ let(:headers) do
200
+ {
201
+ 'link' => [
202
+ '<./history?index=0>; rel="first"',
203
+ '<./history?index=0>; rel="current"',
204
+ '<./history?index=1>; rel="next"'
205
+ ].join(', ')
206
+ }
207
+ end
208
+
209
+ it 'is the first page' do
210
+ expect(subject).to be_first_page
211
+ end
212
+
213
+ it 'is not the last page' do
214
+ expect(subject).to_not be_last_page
215
+ end
216
+
217
+ it 'supports pagination' do
218
+ expect(subject.supports_pagination?).to eql(true)
219
+ end
220
+
221
+ context 'accessing next page' do
222
+ let(:next_body) do
223
+ [ { id: 2 } ]
224
+ end
225
+ let(:next_headers) do
226
+ {
227
+ 'link' => [
228
+ '<./history?index=0>; rel="first"',
229
+ '<./history?index=1>; rel="current"'
230
+ ].join(', ')
231
+ }
232
+ end
233
+ let(:next_http_response) do
234
+ double('http_response', {
235
+ body: next_body,
236
+ headers: next_headers
237
+ })
238
+ end
239
+ let(:subject) { first_paged_request.next_page }
240
+
241
+ before do
242
+ expect(client).to receive(:get).with("#{base_url}/history?index=1").and_return(next_http_response).once
243
+ end
244
+
245
+ it 'returns another PaginatedResource' do
246
+ expect(subject).to be_a(paginated_resource_class)
247
+ end
248
+
249
+ it 'retrieves the next page of results' do
250
+ expect(subject.length).to eql(next_body.length)
251
+ expect(subject[0][:id]).to eql(next_body[0][:id])
252
+ end
253
+
254
+ it 'is not the first page' do
255
+ expect(subject).to_not be_first_page
256
+ end
257
+
258
+ it 'is the last page' do
259
+ expect(subject).to be_last_page
260
+ end
261
+
262
+ it 'raises an exception if trying to access the last page when it is the last page' do
263
+ expect(subject).to be_last_page
264
+ expect { subject.next_page }.to raise_error Ably::Exceptions::InvalidPageError, /There are no more pages/
265
+ end
266
+
267
+ context 'and then first page' do
268
+ before do
269
+ expect(client).to receive(:get).with("#{base_url}/history?index=0").and_return(http_response).once
270
+ end
271
+ subject { first_paged_request.next_page.first_page }
272
+
273
+ it 'returns a PaginatedResource' do
274
+ expect(subject).to be_a(paginated_resource_class)
275
+ end
276
+
277
+ it 'retrieves the first page of results' do
278
+ expect(subject.length).to eql(body.length)
279
+ end
280
+
281
+ it 'is the first page' do
282
+ expect(subject).to be_first_page
283
+ end
284
+ end
285
+ end
286
+ end
287
+ end
288
+