ably-rest 0.7.1 → 0.7.3

Sign up to get free protection for your applications and to get access to all the features.
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,185 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'base64'
4
+
5
+ describe Ably::Models::MessageEncoders do
6
+ let(:default_client_options) { { api_key: api_key, environment: environment } }
7
+ let(:client) { Ably::Rest::Client.new(default_client_options.merge(protocol: protocol)) }
8
+ let(:channel_options) { {} }
9
+ let(:channel) { client.channel('test', channel_options) }
10
+ let(:response) { instance_double('Faraday::Response', status: 201) }
11
+
12
+ let(:cipher_params) { { key: random_str, algorithm: 'aes', mode: 'cbc', key_length: 128 } }
13
+ let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
14
+
15
+ let(:utf_8_data) { random_str.encode(Encoding::UTF_8) }
16
+ let(:binary_data) { MessagePack.pack(random_str).encode(Encoding::ASCII_8BIT) }
17
+ let(:json_data) { { 'key' => random_str } }
18
+
19
+ after do
20
+ channel.publish 'event', published_data
21
+ end
22
+
23
+ def on_publish
24
+ expect(client).to receive(:post) do |url, message|
25
+ yield(message['encoding'], message['data'])
26
+ end.and_return(response)
27
+ end
28
+
29
+ def decrypted(payload, options = {})
30
+ payload = Base64.decode64(payload) if options[:base64]
31
+ crypto.decrypt(payload)
32
+ end
33
+
34
+ context 'with binary transport protocol' do
35
+ let(:protocol) { :msgpack }
36
+
37
+ context 'without encryption' do
38
+ context 'with UTF-8 data' do
39
+ let(:published_data) { utf_8_data }
40
+
41
+ it 'does not apply any encoding' do
42
+ on_publish do |encoding, encoded_data|
43
+ expect(encoding).to be_nil
44
+ expect(encoded_data).to eql(published_data)
45
+ end
46
+ end
47
+ end
48
+
49
+ context 'with binary data' do
50
+ let(:published_data) { binary_data }
51
+
52
+ it 'does not apply any encoding' do
53
+ on_publish do |encoding, encoded_data|
54
+ expect(encoding).to be_nil
55
+ expect(encoded_data).to eql(published_data)
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'with JSON data' do
61
+ let(:published_data) { json_data }
62
+
63
+ it 'stringifies the JSON and sets the encoding attribute to "json"' do
64
+ on_publish do |encoding, encoded_data|
65
+ expect(encoding).to eql('json')
66
+ expect(encoded_data).to eql(JSON.dump(published_data))
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ context 'with encryption' do
73
+ let(:channel_options) { { encrypted: true, cipher_params: cipher_params } }
74
+
75
+ context 'with UTF-8 data' do
76
+ let(:published_data) { utf_8_data }
77
+
78
+ it 'applies utf-8 and cipher encoding and sets the encoding attribute to "utf-8/cipher+aes-128-cbc"' do
79
+ on_publish do |encoding, encoded_data|
80
+ expect(encoding).to eql('utf-8/cipher+aes-128-cbc')
81
+ expect(decrypted(encoded_data)).to eql(published_data)
82
+ end
83
+ end
84
+ end
85
+
86
+ context 'with binary data' do
87
+ let(:published_data) { binary_data }
88
+
89
+ it 'applies cipher encoding and sets the encoding attribute to "cipher+aes-128-cbc"' do
90
+ on_publish do |encoding, encoded_data|
91
+ expect(encoding).to eql('cipher+aes-128-cbc')
92
+ expect(decrypted(encoded_data)).to eql(published_data)
93
+ end
94
+ end
95
+ end
96
+
97
+ context 'with JSON data' do
98
+ let(:published_data) { json_data }
99
+
100
+ it 'applies json, utf-8 and cipher encoding and sets the encoding attribute to "json/utf-8/cipher+aes-128-cbc"' do
101
+ on_publish do |encoding, encoded_data|
102
+ expect(encoding).to eql('json/utf-8/cipher+aes-128-cbc')
103
+ expect(decrypted(encoded_data)).to eql(JSON.dump(published_data))
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ context 'with text transport protocol' do
111
+ let(:protocol) { :json }
112
+
113
+ context 'without encryption' do
114
+ context 'with UTF-8 data' do
115
+ let(:published_data) { utf_8_data }
116
+
117
+ it 'does not apply any encoding' do
118
+ on_publish do |encoding, encoded_data|
119
+ expect(encoding).to be_nil
120
+ expect(encoded_data).to eql(published_data)
121
+ end
122
+ end
123
+ end
124
+
125
+ context 'with binary data' do
126
+ let(:published_data) { binary_data }
127
+
128
+ it 'applies a base64 encoding and sets the encoding attribute to "base64"' do
129
+ on_publish do |encoding, encoded_data|
130
+ expect(encoding).to eql('base64')
131
+ expect(Base64.decode64(encoded_data)).to eql(published_data)
132
+ end
133
+ end
134
+ end
135
+
136
+ context 'with JSON data' do
137
+ let(:published_data) { json_data }
138
+
139
+ it 'stringifies the JSON and sets the encoding attribute to "json"' do
140
+ on_publish do |encoding, encoded_data|
141
+ expect(encoding).to eql('json')
142
+ expect(encoded_data).to eql(JSON.dump(published_data))
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ context 'with encryption' do
149
+ let(:channel_options) { { encrypted: true, cipher_params: cipher_params } }
150
+
151
+ context 'with UTF-8 data' do
152
+ let(:published_data) { utf_8_data }
153
+
154
+ it 'applies utf-8, cipher and base64 encodings and sets the encoding attribute to "utf-8/cipher+aes-128-cbc/base64"' do
155
+ on_publish do |encoding, encoded_data|
156
+ expect(encoding).to eql('utf-8/cipher+aes-128-cbc/base64')
157
+ expect(decrypted(encoded_data, base64: true)).to eql(published_data)
158
+ end
159
+ end
160
+ end
161
+
162
+ context 'with binary data' do
163
+ let(:published_data) { binary_data }
164
+
165
+ it 'applies cipher and base64 encoding and sets the encoding attribute to "utf-8/cipher+aes-128-cbc/base64"' do
166
+ on_publish do |encoding, encoded_data|
167
+ expect(encoding).to eql('cipher+aes-128-cbc/base64')
168
+ expect(decrypted(encoded_data, base64: true)).to eql(published_data)
169
+ end
170
+ end
171
+ end
172
+
173
+ context 'with JSON data' do
174
+ let(:published_data) { json_data }
175
+
176
+ it 'applies json, utf-8, cipher and base64 encoding and sets the encoding attribute to "json/utf-8/cipher+aes-128-cbc/base64"' do
177
+ on_publish do |encoding, encoded_data|
178
+ expect(encoding).to eql('json/utf-8/cipher+aes-128-cbc/base64')
179
+ expect(decrypted(encoded_data, base64: true)).to eql(JSON.dump(published_data))
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
@@ -0,0 +1,247 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+ require 'securerandom'
4
+
5
+ describe Ably::Rest::Channel, 'messages' do
6
+ include Ably::Modules::Conversions
7
+
8
+ vary_by_protocol do
9
+ let(:default_client_options) { { api_key: api_key, environment: environment, protocol: protocol } }
10
+ let(:client_options) { default_client_options }
11
+ let(:client) { Ably::Rest::Client.new(client_options) }
12
+ let(:other_client) { Ably::Rest::Client.new(client_options) }
13
+ let(:channel) { client.channel('test') }
14
+
15
+ context 'publishing with an ASCII_8BIT message name' do
16
+ let(:message_name) { random_str.encode(Encoding::ASCII_8BIT) }
17
+
18
+ it 'is converted into UTF_8' do
19
+ channel.publish message_name, 'example'
20
+ message = channel.history.first
21
+ expect(message.name.encoding).to eql(Encoding::UTF_8)
22
+ expect(message.name.encode(Encoding::ASCII_8BIT)).to eql(message_name)
23
+ end
24
+ end
25
+
26
+ describe 'encryption and encoding' do
27
+ let(:channel_name) { "persisted:#{random_str}" }
28
+ let(:cipher_options) { { key: random_str(32) } }
29
+ let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
30
+
31
+ context 'with #publish and #history' do
32
+ shared_examples 'an Ably encrypter and decrypter' do |item, data|
33
+ let(:algorithm) { data['algorithm'].upcase }
34
+ let(:mode) { data['mode'].upcase }
35
+ let(:key_length) { data['keylength'] }
36
+ let(:secret_key) { Base64.decode64(data['key']) }
37
+ let(:iv) { Base64.decode64(data['iv']) }
38
+
39
+ let(:cipher_options) { { key: secret_key, iv: iv, algorithm: algorithm, mode: mode, key_length: key_length } }
40
+
41
+ let(:encoded) { item['encoded'] }
42
+ let(:encoded_data) { encoded['data'] }
43
+ let(:encoded_encoding) { encoded['encoding'] }
44
+ let(:encoded_data_decoded) do
45
+ if encoded_encoding == 'json'
46
+ JSON.parse(encoded_data)
47
+ elsif encoded_encoding == 'base64'
48
+ Base64.decode64(encoded_data)
49
+ else
50
+ encoded_data
51
+ end
52
+ end
53
+
54
+ let(:encrypted) { item['encrypted'] }
55
+ let(:encrypted_data) { encrypted['data'] }
56
+ let(:encrypted_encoding) { encrypted['encoding'] }
57
+ let(:encrypted_data_decoded) do
58
+ if encrypted_encoding.match(%r{/base64$})
59
+ Base64.decode64(encrypted_data)
60
+ else
61
+ encrypted_data
62
+ end
63
+ end
64
+
65
+ it 'encrypts message automatically when published' do
66
+ expect(client).to receive(:post) do |path, message|
67
+ if protocol == :json
68
+ expect(message['encoding']).to eql(encrypted_encoding)
69
+ expect(Base64.decode64(message['data'])).to eql(encrypted_data_decoded)
70
+ else
71
+ # Messages sent over binary protocol will not have Base64 encoded data
72
+ expect(message['encoding']).to eql(encrypted_encoding.gsub(%r{/base64$}, ''))
73
+ expect(message['data']).to eql(encrypted_data_decoded)
74
+ end
75
+ end.and_return(double('Response', status: 201))
76
+
77
+ encrypted_channel.publish 'example', encoded_data_decoded
78
+ end
79
+
80
+ it 'sends and retrieves messages that are encrypted & decrypted by the Ably library' do
81
+ encrypted_channel.publish 'example', encoded_data_decoded
82
+
83
+ message = encrypted_channel.history.first
84
+ expect(message.data).to eql(encoded_data_decoded)
85
+ expect(message.encoding).to be_nil
86
+ end
87
+ end
88
+
89
+ resources_root = File.expand_path('../../../resources', __FILE__)
90
+
91
+ def self.add_tests_for_data(data)
92
+ data['items'].each_with_index do |item, index|
93
+ context "item #{index} with encrypted encoding #{item['encrypted']['encoding']}" do
94
+ it_behaves_like 'an Ably encrypter and decrypter', item, data
95
+ end
96
+ end
97
+ end
98
+
99
+ context 'with AES-128-CBC using crypto-data-128.json fixtures' do
100
+ data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-128.json')))
101
+ add_tests_for_data data
102
+ end
103
+
104
+ context 'with AES-256-CBC using crypto-data-256.json fixtures' do
105
+ data = JSON.parse(File.read(File.join(resources_root, 'crypto-data-256.json')))
106
+ add_tests_for_data data
107
+ end
108
+
109
+ context 'when publishing lots of messages' do
110
+ let(:data) { MessagePack.pack({ 'key' => random_str }) }
111
+ let(:message_count) { 20 }
112
+
113
+ it 'encrypts on #publish and decrypts on #history' do
114
+ message_count.times do |index|
115
+ encrypted_channel.publish index.to_s, "#{index}-#{data}"
116
+ end
117
+
118
+ messages = encrypted_channel.history
119
+
120
+ expect(messages.count).to eql(message_count)
121
+ messages.each do |message|
122
+ expect(message.data).to eql("#{message.name}-#{data}")
123
+ expect(message.encoding).to be_nil
124
+ end
125
+ end
126
+ end
127
+
128
+ context 'when retrieving #history with a different protocol' do
129
+ let(:other_protocol) { protocol == :msgpack ? :json : :msgpack }
130
+ let(:other_client) { Ably::Rest::Client.new(default_client_options.merge(protocol: other_protocol)) }
131
+ let(:other_client_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
132
+
133
+ before do
134
+ expect(other_client.protocol_binary?).to_not eql(client.protocol_binary?)
135
+ end
136
+
137
+ [MessagePack.pack({ 'key' => SecureRandom.hex }), 'ã unicode', { 'key' => SecureRandom.hex }].each do |payload|
138
+ payload_description = "#{payload.class}#{" #{payload.encoding}" if payload.kind_of?(String)}"
139
+
140
+ specify "delivers a #{payload_description} payload to the receiver" do
141
+ encrypted_channel.publish 'example', payload
142
+
143
+ message = other_client_channel.history.first
144
+ expect(message.data).to eql(payload)
145
+ expect(message.encoding).to be_nil
146
+ end
147
+ end
148
+ end
149
+
150
+ context 'when publishing on an unencrypted channel and retrieving with #history on an encrypted channel' do
151
+ let(:unencrypted_channel) { client.channel(channel_name) }
152
+ let(:other_client_encrypted_channel) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
153
+
154
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
155
+
156
+ it 'does not attempt to decrypt the message' do
157
+ unencrypted_channel.publish 'example', payload
158
+
159
+ message = other_client_encrypted_channel.history.first
160
+ expect(message.data).to eql(payload)
161
+ expect(message.encoding).to be_nil
162
+ end
163
+ end
164
+
165
+ context 'when publishing on an encrypted channel and retrieving with #history on an unencrypted channel' do
166
+ let(:client_options) { default_client_options.merge(log_level: :fatal) }
167
+ let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
168
+ let(:encrypted_channel) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options) }
169
+ let(:other_client_unencrypted_channel) { other_client.channel(channel_name) }
170
+
171
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
172
+
173
+ before do
174
+ encrypted_channel.publish 'example', payload
175
+ end
176
+
177
+ it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
178
+ message = other_client_unencrypted_channel.history.first
179
+ expect(message.data).to_not eql(payload)
180
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
181
+ end
182
+
183
+ it 'logs a Cipher exception' do
184
+ expect(other_client.logger).to receive(:error) do |message|
185
+ expect(message).to match(/Message cannot be decrypted/)
186
+ end
187
+ other_client_unencrypted_channel.history
188
+ end
189
+ end
190
+
191
+ context 'publishing on an encrypted channel and retrieving #history with a different algorithm on another client' do
192
+ let(:client_options) { default_client_options.merge(log_level: :fatal) }
193
+ let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
194
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
195
+ let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
196
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
197
+
198
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
199
+
200
+ before do
201
+ encrypted_channel_client1.publish 'example', payload
202
+ end
203
+
204
+ it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
205
+ message = encrypted_channel_client2.history.first
206
+ expect(message.data).to_not eql(payload)
207
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
208
+ end
209
+
210
+ it 'logs a Cipher exception' do
211
+ expect(other_client.logger).to receive(:error) do |message|
212
+ expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
213
+ end
214
+ encrypted_channel_client2.history
215
+ end
216
+ end
217
+
218
+ context 'publishing on an encrypted channel and subscribing with a different key on another client' do
219
+ let(:client_options) { default_client_options.merge(log_level: :fatal) }
220
+ let(:cipher_options_client1) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
221
+ let(:encrypted_channel_client1) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client1) }
222
+ let(:cipher_options_client2) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
223
+ let(:encrypted_channel_client2) { other_client.channel(channel_name, encrypted: true, cipher_params: cipher_options_client2) }
224
+
225
+ let(:payload) { MessagePack.pack({ 'key' => random_str }) }
226
+
227
+ before do
228
+ encrypted_channel_client1.publish 'example', payload
229
+ end
230
+
231
+ it 'retrieves the message that remains encrypted with an encrypted encoding attribute' do
232
+ message = encrypted_channel_client2.history.first
233
+ expect(message.data).to_not eql(payload)
234
+ expect(message.encoding).to match(/^cipher\+aes-256-cbc/)
235
+ end
236
+
237
+ it 'logs a Cipher exception' do
238
+ expect(other_client.logger).to receive(:error) do |message|
239
+ expect(message).to match(/CipherError decrypting data/)
240
+ end
241
+ encrypted_channel_client2.history
242
+ end
243
+ end
244
+ end
245
+ end
246
+ end
247
+ end
@@ -0,0 +1,292 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest::Presence do
5
+ include Ably::Modules::Conversions
6
+
7
+ vary_by_protocol do
8
+ let(:default_options) { { api_key: api_key, environment: environment, protocol: protocol } }
9
+ let(:client_options) { default_options }
10
+ let(:client) do
11
+ Ably::Rest::Client.new(client_options)
12
+ end
13
+
14
+ let(:fixtures) do
15
+ TestApp::APP_SPEC['channels'].first['presence'].map do |fixture|
16
+ IdiomaticRubyWrapper(fixture, stop_at: [:data])
17
+ end
18
+ end
19
+
20
+ context 'tested against presence fixture data set up in test app' do
21
+ describe '#get' do
22
+ before(:context) do
23
+ # When this test is run as a part of a test suite, the presence data injected in the test app may have expired
24
+ reload_test_app
25
+ end
26
+
27
+ let(:channel) { client.channel('persisted:presence_fixtures') }
28
+ let(:presence) { channel.presence.get }
29
+
30
+ it 'returns current members on the channel with their action set to :present' do
31
+ expect(presence.size).to eql(4)
32
+
33
+ fixtures.each do |fixture|
34
+ presence_message = presence.find { |client| client.client_id == fixture[:client_id] }
35
+ expect(presence_message.data).to eq(fixture[:data])
36
+ expect(presence_message.action).to eq(:present)
37
+ end
38
+ end
39
+
40
+ context 'with :limit option' do
41
+ let(:page_size) { 2 }
42
+ let(:presence) { channel.presence.get(limit: page_size) }
43
+
44
+ it 'returns a paged response limiting number of members per page' do
45
+ expect(presence.size).to eql(2)
46
+ next_page = presence.next_page
47
+ expect(next_page.size).to eql(2)
48
+ expect(next_page).to be_last_page
49
+ end
50
+ end
51
+ end
52
+
53
+ describe '#history' do
54
+ before(:context) do
55
+ # When this test is run as a part of a test suite, the presence data injected in the test app may have expired
56
+ reload_test_app
57
+ end
58
+
59
+ let(:channel) { client.channel('persisted:presence_fixtures') }
60
+ let(:presence_history) { channel.presence.history }
61
+
62
+ it 'returns recent presence activity' do
63
+ expect(presence_history.size).to eql(4)
64
+
65
+ fixtures.each do |fixture|
66
+ presence_message = presence_history.find { |client| client.client_id == fixture['clientId'] }
67
+ expect(presence_message.data).to eq(fixture[:data])
68
+ end
69
+ end
70
+
71
+ context 'with options' do
72
+ let(:page_size) { 2 }
73
+
74
+ context 'direction: :forwards' do
75
+ let(:presence_history) { channel.presence.history(direction: :forwards) }
76
+ let(:paged_history_forward) { channel.presence.history(limit: page_size, direction: :forwards) }
77
+
78
+ it 'returns recent presence activity forwards with most recent history last' do
79
+ expect(paged_history_forward).to be_a(Ably::Models::PaginatedResource)
80
+ expect(paged_history_forward.size).to eql(2)
81
+
82
+ next_page = paged_history_forward.next_page
83
+
84
+ expect(paged_history_forward.first.id).to eql(presence_history.first.id)
85
+ expect(next_page.first.id).to eql(presence_history[page_size].id)
86
+ end
87
+ end
88
+
89
+ context 'direction: :backwards' do
90
+ let(:presence_history) { channel.presence.history(direction: :backwards) }
91
+ let(:paged_history_backward) { channel.presence.history(limit: page_size, direction: :backwards) }
92
+
93
+ it 'returns recent presence activity backwards with most recent history first' do
94
+ expect(paged_history_backward).to be_a(Ably::Models::PaginatedResource)
95
+ expect(paged_history_backward.size).to eql(2)
96
+
97
+ next_page = paged_history_backward.next_page
98
+
99
+ expect(paged_history_backward.first.id).to eql(presence_history.first.id)
100
+ expect(next_page.first.id).to eql(presence_history[page_size].id)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ describe '#history' do
108
+ context 'with time range options' do
109
+ let(:channel_name) { "persisted:#{random_str(4)}" }
110
+ let(:presence) { client.channel(channel_name).presence }
111
+ let(:user) { 'appid.keyuid' }
112
+ let(:secret) { random_str(8) }
113
+ let(:endpoint) do
114
+ client.endpoint.tap do |client_end_point|
115
+ client_end_point.user = user
116
+ client_end_point.password = secret
117
+ end
118
+ end
119
+ let(:client) do
120
+ Ably::Rest::Client.new(api_key: "#{user}:#{secret}")
121
+ end
122
+
123
+ [:start, :end].each do |option|
124
+ describe ":#{option}", :webmock do
125
+ let!(:history_stub) {
126
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?#{option}=#{milliseconds}").
127
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
128
+ }
129
+
130
+ before do
131
+ presence.history(options)
132
+ end
133
+
134
+ context 'with milliseconds since epoch value' do
135
+ let(:milliseconds) { as_since_epoch(Time.now) }
136
+ let(:options) { { option => milliseconds } }
137
+
138
+ it 'uses this value in the history request' do
139
+ expect(history_stub).to have_been_requested
140
+ end
141
+ end
142
+
143
+ context 'with Time object value' do
144
+ let(:time) { Time.now }
145
+ let(:milliseconds) { as_since_epoch(time) }
146
+ let(:options) { { option => time } }
147
+
148
+ it 'converts the value to milliseconds since epoch in the hisotry request' do
149
+ expect(history_stub).to have_been_requested
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ describe 'decoding', :webmock do
158
+ let(:user) { 'appid.keyuid' }
159
+ let(:secret) { random_str(8) }
160
+ let(:endpoint) do
161
+ client.endpoint.tap do |client_end_point|
162
+ client_end_point.user = user
163
+ client_end_point.password = secret
164
+ end
165
+ end
166
+ let(:client) do
167
+ Ably::Rest::Client.new(client_options.merge(api_key: "#{user}:#{secret}"))
168
+ end
169
+
170
+ let(:data) { random_str(32) }
171
+ let(:channel_name) { "persisted:#{random_str(4)}" }
172
+ let(:cipher_options) { { key: random_str(32), algorithm: 'aes', mode: 'cbc', key_length: 256 } }
173
+ let(:presence) { client.channel(channel_name, encrypted: true, cipher_params: cipher_options).presence }
174
+
175
+ let(:crypto) { Ably::Util::Crypto.new(cipher_options) }
176
+
177
+ let(:content_type) do
178
+ if protocol == :msgpack
179
+ 'application/x-msgpack'
180
+ else
181
+ 'application/json'
182
+ end
183
+ end
184
+
185
+ context 'valid decodeable content' do
186
+ let(:serialized_encoded_message) do
187
+ if protocol == :msgpack
188
+ msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-256-cbc' })
189
+ MessagePack.pack([msg.as_json])
190
+ else
191
+ msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-256-cbc/base64' })
192
+ [msg].to_json
193
+ end
194
+ end
195
+
196
+ context '#get' do
197
+ let!(:get_stub) {
198
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
199
+ to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
200
+ }
201
+
202
+ after do
203
+ expect(get_stub).to have_been_requested
204
+ end
205
+
206
+ it 'automaticaly decodes presence messages' do
207
+ present = presence.get
208
+ expect(present.first.encoding).to be_nil
209
+ expect(present.first.data).to eql(data)
210
+ end
211
+ end
212
+
213
+ context '#history' do
214
+ let!(:history_stub) {
215
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
216
+ to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
217
+ }
218
+
219
+ after do
220
+ expect(history_stub).to have_been_requested
221
+ end
222
+
223
+ it 'automaticaly decodes presence messages' do
224
+ history = presence.history
225
+ expect(history.first.encoding).to be_nil
226
+ expect(history.first.data).to eql(data)
227
+ end
228
+ end
229
+ end
230
+
231
+ context 'invalid data' do
232
+ let(:serialized_encoded_message_with_invalid_encoding) do
233
+ if protocol == :msgpack
234
+ msg = Ably::Models::PresenceMessage.new({ action: :enter, data: crypto.encrypt(data), encoding: 'utf-8/cipher+aes-128-cbc' })
235
+ MessagePack.pack([msg.as_json])
236
+ else
237
+ msg = Ably::Models::PresenceMessage.new({ action: :enter, data: Base64.encode64(crypto.encrypt(data)), encoding: 'utf-8/cipher+aes-128-cbc/base64' })
238
+ [msg].to_json
239
+ end
240
+ end
241
+
242
+ context '#get' do
243
+ let(:client_options) { default_options.merge(log_level: :fatal) }
244
+ let!(:get_stub) {
245
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
246
+ to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
247
+ }
248
+ let(:presence_message) { presence.get.first }
249
+
250
+ after do
251
+ expect(get_stub).to have_been_requested
252
+ end
253
+
254
+ it 'returns the messages still encoded' do
255
+ expect(presence_message.encoding).to match(/cipher\+aes-128-cbc/)
256
+ end
257
+
258
+ it 'logs a cipher error' do
259
+ expect(client.logger).to receive(:error) do |message|
260
+ expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
261
+ end
262
+ presence.get
263
+ end
264
+ end
265
+
266
+ context '#history' do
267
+ let(:client_options) { default_options.merge(log_level: :fatal) }
268
+ let!(:history_stub) {
269
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
270
+ to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
271
+ }
272
+ let(:presence_message) { presence.history.first }
273
+
274
+ after do
275
+ expect(history_stub).to have_been_requested
276
+ end
277
+
278
+ it 'returns the messages still encoded' do
279
+ expect(presence_message.encoding).to match(/cipher\+aes-128-cbc/)
280
+ end
281
+
282
+ it 'logs a cipher error' do
283
+ expect(client.logger).to receive(:error) do |message|
284
+ expect(message).to match(/Cipher algorithm [\w-]+ does not match/)
285
+ end
286
+ presence.history
287
+ end
288
+ end
289
+ end
290
+ end
291
+ end
292
+ end