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,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