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,32 @@
1
+ module RSpec
2
+ module ProtocolHelper
3
+ PROTOCOLS = if ENV['TEST_LIMIT_PROTOCOLS']
4
+ JSON.parse(ENV['TEST_LIMIT_PROTOCOLS'])
5
+ else
6
+ {
7
+ json: 'JSON',
8
+ msgpack: 'MsgPack'
9
+ }
10
+ end
11
+
12
+ def vary_by_protocol(&block)
13
+ RSpec::ProtocolHelper::PROTOCOLS.each do |protocol, description|
14
+ context("using #{description} protocol", protocol: protocol, &block)
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ RSpec.configure do |config|
21
+ config.extend RSpec::ProtocolHelper
22
+
23
+ config.before(:context, protocol: :json) do |context|
24
+ context.class.let(:protocol) { :json }
25
+ end
26
+
27
+ config.before(:context, protocol: :msgpack) do |context|
28
+ context.class.let(:protocol) { :msgpack }
29
+ end
30
+ end
31
+
32
+
@@ -0,0 +1,15 @@
1
+ require 'securerandom'
2
+
3
+ module RandomHelper
4
+ def random_str(length = 16)
5
+ SecureRandom.hex(length).encode(Encoding::UTF_8)
6
+ end
7
+
8
+ def random_int_str(size = 1_000_000_000)
9
+ SecureRandom.random_number(size).to_s.encode(Encoding::UTF_8)
10
+ end
11
+
12
+ RSpec.configure do |config|
13
+ config.include self
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # If a test fails and RSPEC_RETRY is set to true, create a new
2
+ # application before retrying the RSpec test again
3
+ #
4
+ RSpec.configure do |config|
5
+ config.around(:example) do |example|
6
+ example.run
7
+
8
+ next if example.metadata[:webmock] # new app is not needed for a mocked test
9
+
10
+ if example.exception && ENV['RSPEC_RETRY']
11
+ reload_test_app
12
+ puts "** Test app reloaded before next retry **"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,113 @@
1
+ require 'singleton'
2
+
3
+ class TestApp
4
+ APP_SPEC = {
5
+ 'keys' => [
6
+ {},
7
+ {
8
+ 'capability' => '{ "cansubscribe:*":["subscribe"], "canpublish:*":["publish"], "canpublish:andpresence":["presence","publish"] }'
9
+ }
10
+ ],
11
+ 'namespaces' => [
12
+ { 'id' => 'persisted', 'persisted' => true }
13
+ ],
14
+ 'channels' => [
15
+ {
16
+ 'name' => 'persisted:presence_fixtures',
17
+ 'presence' => [
18
+ { 'clientId' => 'client_bool', 'clientData' => 'true' },
19
+ { 'clientId' => 'client_int', 'clientData' => '24' },
20
+ { 'clientId' => 'client_string', 'clientData' => 'This is a string clientData payload' },
21
+ { 'clientId' => 'client_json', 'clientData' => '{ "test" => \'This is a JSONObject clientData payload\'}' }
22
+ ]
23
+ }
24
+ ]
25
+ }
26
+
27
+ # If an app has already been created and we need a new app, create a new test app
28
+ # This is sometimes needed when a test needs to be isolated from any other tests
29
+ def self.reload
30
+ if instance_variable_get('@singleton__instance__')
31
+ instance.delete
32
+ instance.create_test_app
33
+ end
34
+ end
35
+
36
+ include Singleton
37
+
38
+ def initialize
39
+ create_test_app
40
+ end
41
+
42
+ def app_id
43
+ @attributes["appId"]
44
+ end
45
+
46
+ def key
47
+ @attributes["keys"].first
48
+ end
49
+
50
+ def restricted_key
51
+ @attributes["keys"][1]
52
+ end
53
+
54
+ def key_id
55
+ "#{app_id}.#{key['id']}"
56
+ end
57
+
58
+ def key_value
59
+ key['value']
60
+ end
61
+
62
+ def api_key
63
+ "#{key_id}:#{key_value}"
64
+ end
65
+
66
+ def restricted_api_key
67
+ "#{app_id}.#{restricted_key['id']}:#{restricted_key['value']}"
68
+ end
69
+
70
+ def delete
71
+ return unless TestApp.instance_variable_get('@singleton__instance__')
72
+
73
+ url = "#{sandbox_client.endpoint}/apps/#{app_id}"
74
+
75
+ basic_auth = Base64.encode64(api_key).chomp
76
+ headers = { "Authorization" => "Basic #{basic_auth}" }
77
+
78
+ Faraday.delete(url, nil, headers)
79
+ end
80
+
81
+ def environment
82
+ 'sandbox'
83
+ end
84
+
85
+ def create_test_app
86
+ url = "#{sandbox_client.endpoint}/apps"
87
+
88
+ headers = {
89
+ 'Accept' => 'application/json',
90
+ 'Content-Type' => 'application/json'
91
+ }
92
+
93
+ @attributes = JSON.parse(Faraday.post(url, APP_SPEC.to_json, headers).body)
94
+ end
95
+
96
+ def host
97
+ sandbox_client.endpoint.host
98
+ end
99
+
100
+ def realtime_host
101
+ host.gsub(/rest/, 'realtime')
102
+ end
103
+
104
+ def create_test_stats(stats)
105
+ client = Ably::Rest::Client.new(api_key: api_key, environment: environment)
106
+ client.post('/stats', stats)
107
+ end
108
+
109
+ private
110
+ def sandbox_client
111
+ @sandbox_client ||= Ably::Rest::Client.new(api_key: 'app.key:secret', tls: true, environment: environment)
112
+ end
113
+ end
@@ -0,0 +1,68 @@
1
+ require 'spec_helper'
2
+ require 'shared/protocol_msgbus_behaviour'
3
+
4
+ describe Ably::Auth do
5
+ let(:client) { double('client').as_null_object }
6
+ let(:client_id) { nil }
7
+ let(:options) { { api_key: 'appid.keyuid:keysecret', client_id: client_id } }
8
+
9
+ subject do
10
+ Ably::Auth.new(client, options)
11
+ end
12
+
13
+ describe 'client_id option' do
14
+ let(:client_id) { random_str.encode(encoding) }
15
+
16
+ context 'with nil value' do
17
+ let(:client_id) { nil }
18
+
19
+ it 'is permitted' do
20
+ expect(subject.client_id).to be_nil
21
+ end
22
+ end
23
+
24
+ context 'as UTF_8 string' do
25
+ let(:encoding) { Encoding::UTF_8 }
26
+
27
+ it 'is permitted' do
28
+ expect(subject.client_id).to eql(client_id)
29
+ end
30
+
31
+ it 'remains as UTF-8' do
32
+ expect(subject.client_id.encoding).to eql(encoding)
33
+ end
34
+ end
35
+
36
+ context 'as SHIFT_JIS string' do
37
+ let(:encoding) { Encoding::SHIFT_JIS }
38
+
39
+ it 'gets converted to UTF-8' do
40
+ expect(subject.client_id.encoding).to eql(Encoding::UTF_8)
41
+ end
42
+
43
+ it 'is compatible with original encoding' do
44
+ expect(subject.client_id.encode(encoding)).to eql(client_id)
45
+ end
46
+ end
47
+
48
+ context 'as ASCII_8BIT string' do
49
+ let(:encoding) { Encoding::ASCII_8BIT }
50
+
51
+ it 'gets converted to UTF-8' do
52
+ expect(subject.client_id.encoding).to eql(Encoding::UTF_8)
53
+ end
54
+
55
+ it 'is compatible with original encoding' do
56
+ expect(subject.client_id.encode(encoding)).to eql(client_id)
57
+ end
58
+ end
59
+
60
+ context 'as Integer' do
61
+ let(:client_id) { 1 }
62
+
63
+ it 'raises an argument error' do
64
+ expect { subject.client_id }.to raise_error ArgumentError, /must be a String/
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ably::Logger do
4
+ let(:rest_client) do
5
+ instance_double('Ably::Rest::Client')
6
+ end
7
+
8
+ subject { Ably::Logger.new(rest_client, Logger::INFO) }
9
+
10
+ def uncolorize(string)
11
+ regex_pattern = /\033\[[0-9]+m(.+?)\033\[0m/m
12
+ string.gsub(regex_pattern, '\1')
13
+ end
14
+
15
+ it 'uses the language provided Logger by default' do
16
+ expect(subject.logger).to be_a(Logger)
17
+ end
18
+
19
+ context 'internals', :api_private do
20
+ it 'delegates to the logger object' do
21
+ expect(subject.logger).to receive(:warn).with('message')
22
+ subject.warn 'message'
23
+ end
24
+
25
+ context 'formatter' do
26
+ context 'when debugging' do
27
+ it 'uses short time format' do
28
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
29
+ formatted = uncolorize(formatted)
30
+ expect(formatted).to match(/^\d+:\d+:\d+.\d{3} DEBUG/)
31
+ end
32
+ end
33
+
34
+ context 'when info -> fatal' do
35
+ it 'uses long time format' do
36
+ formatted = subject.logger.formatter.call(Logger::INFO, Time.now, 'progid', 'unique_message')
37
+ formatted = uncolorize(formatted)
38
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
39
+ end
40
+ end
41
+
42
+ if defined?(Ably::Realtime)
43
+ context 'with Realtime client' do
44
+ let(:new_realtime_client) do
45
+ instance_double('Ably::Realtime::Client', connection: instance_double('Ably::Realtime::Connection', id: nil))
46
+ end
47
+ let(:connected_realtime_client) do
48
+ instance_double('Ably::Realtime::Client', connection: instance_double('Ably::Realtime::Connection', id: '0000'))
49
+ end
50
+ before do
51
+ allow(new_realtime_client).to receive(:kind_of?).with(Ably::Realtime::Client).and_return(true)
52
+ allow(connected_realtime_client).to receive(:kind_of?).with(Ably::Realtime::Client).and_return(true)
53
+ end
54
+
55
+ context 'with Realtime disconnected client' do
56
+ subject { Ably::Logger.new(new_realtime_client, Logger::INFO) }
57
+
58
+ it 'formats logs with an empty client ID' do
59
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
60
+ formatted = uncolorize(formatted)
61
+ expect(formatted).to match(/\[ \-\- \]/)
62
+ expect(formatted).to match(%r{unique_message$})
63
+ expect(formatted).to match(%r{DEBUG})
64
+ end
65
+ end
66
+
67
+ context 'with Realtime connected client' do
68
+ subject { Ably::Logger.new(connected_realtime_client, Logger::INFO) }
69
+
70
+ it 'formats logs with a client ID' do
71
+ formatted = subject.logger.formatter.call(Logger::DEBUG, Time.now, 'progid', 'unique_message')
72
+ formatted = uncolorize(formatted)
73
+ expect(formatted).to match(/\[0000\]/)
74
+ expect(formatted).to match(%r{unique_message$})
75
+ expect(formatted).to match(%r{DEBUG})
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ context 'with REST client' do
82
+ subject { Ably::Logger.new(rest_client, Logger::INFO) }
83
+
84
+ it 'formats logs without a client ID' do
85
+ formatted = subject.logger.formatter.call(Logger::FATAL, Time.now, 'progid', 'unique_message')
86
+ formatted = uncolorize(formatted)
87
+ expect(formatted).to_not match(/\[.*\]/)
88
+ expect(formatted).to match(%r{unique_message$})
89
+ expect(formatted).to match(%r{FATAL})
90
+ end
91
+ end
92
+
93
+ context 'severity argument' do
94
+ it 'can be an Integer' do
95
+ formatted = subject.logger.formatter.call(Logger::INFO, Time.now, 'progid', 'unique_message')
96
+ formatted = uncolorize(formatted)
97
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
98
+ end
99
+
100
+ it 'can be a string' do
101
+ formatted = subject.logger.formatter.call('INFO', Time.now, 'progid', 'unique_message')
102
+ formatted = uncolorize(formatted)
103
+ expect(formatted).to match(/^\d+-\d+-\d+ \d+:\d+:\d+.\d{3} INFO/)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'with a custom Logger' do
110
+ context 'with an invalid interface' do
111
+ let(:custom_logger_with_bad_interface) do
112
+ Class.new.new
113
+ end
114
+ subject { Ably::Logger.new(rest_client, Logger::INFO, custom_logger_with_bad_interface) }
115
+
116
+ it 'raises an exception' do
117
+ expect { subject }.to raise_error ArgumentError, /The custom Logger's interface does not provide the method/
118
+ end
119
+ end
120
+
121
+ context 'with a valid interface' do
122
+ let(:custom_logger) do
123
+ Class.new do
124
+ extend Forwardable
125
+ def initialize
126
+ @logger = Logger.new(STDOUT)
127
+ end
128
+ def_delegators :@logger, :fatal, :error, :warn, :info, :debug, :level, :level=
129
+ end
130
+ end
131
+ let(:custom_logger_object) { custom_logger.new }
132
+
133
+ subject { Ably::Logger.new(rest_client, Logger::INFO, custom_logger_object) }
134
+
135
+ it 'is used' do
136
+ expect { subject }.to_not raise_error
137
+ expect(subject.logger.class).to eql(custom_logger)
138
+ end
139
+
140
+ it 'delegates log messages to logger', :api_private do
141
+ expect(custom_logger_object).to receive(:fatal).with('message')
142
+ subject.fatal 'message'
143
+ end
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::ErrorInfo do
5
+ subject { Ably::Models::ErrorInfo }
6
+
7
+ it_behaves_like 'a model', with_simple_attributes: %w(code status_code message) do
8
+ let(:model_args) { [] }
9
+ end
10
+
11
+ context '#status' do
12
+ subject { Ably::Models::ErrorInfo.new('statusCode' => 401) }
13
+ it 'is an alias for #status_code' do
14
+ expect(subject.status).to eql(subject.status_code)
15
+ expect(subject.status).to eql(401)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,349 @@
1
+ require 'spec_helper'
2
+
3
+ describe Ably::Models::IdiomaticRubyWrapper, :api_private do
4
+ include Ably::Modules::Conversions
5
+
6
+ let(:mixed_case_data) do
7
+ {
8
+ 'mixedCase' => 'true',
9
+ 'simple' => 'without case',
10
+ 'hashObject' => {
11
+ 'mixedCaseChild' => 'exists'
12
+ },
13
+ 'arrayObject' => [
14
+ {
15
+ 'mixedCaseChild' => 'exists'
16
+ }
17
+ ]
18
+ }
19
+ end
20
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data) }
21
+
22
+ context 'Kernel.Array like method to create a IdiomaticRubyWrapper' do
23
+ it 'will return the same IdiomaticRubyWrapper if passed in' do
24
+ expect(IdiomaticRubyWrapper(subject)).to eql(subject)
25
+ end
26
+
27
+ it 'will return the same IdiomaticRubyWrapper if passed in' do
28
+ expect(IdiomaticRubyWrapper(mixed_case_data)).to be_a(Ably::Models::IdiomaticRubyWrapper)
29
+ end
30
+ end
31
+
32
+ it 'provides accessor method to values using snake_case' do
33
+ expect(subject[:mixed_case]).to eql('true')
34
+ expect(subject[:simple]).to eql('without case')
35
+ end
36
+
37
+ it 'provides methods to read values using snake_case' do
38
+ expect(subject.mixed_case).to eql('true')
39
+ expect(subject.simple).to eql('without case')
40
+ end
41
+
42
+ it 'provides accessor set method to values using snake_case' do
43
+ subject[:mixed_case] = 'mixedCase'
44
+ subject[:simple] = 'simple'
45
+ expect(subject[:mixed_case]).to eql('mixedCase')
46
+ expect(subject[:simple]).to eql('simple')
47
+ end
48
+
49
+ it 'provides methods to write values using snake_case' do
50
+ subject.mixed_case = 'mixedCase'
51
+ subject.simple = 'simple'
52
+ expect(subject.mixed_case).to eql('mixedCase')
53
+ expect(subject.simple).to eql('simple')
54
+ end
55
+
56
+ it 'does not provide methods for keys that are missing' do
57
+ expect { subject.no_key_exists_for_this }.to raise_error NoMethodError
58
+ end
59
+
60
+ specify '#hash returns raw Hash object' do
61
+ expect(subject.hash).to eql(mixed_case_data)
62
+ end
63
+
64
+ context 'recursively wrapping child objects' do
65
+ it 'wraps Hashes' do
66
+ expect(subject.hash_object.mixed_case_child).to eql('exists')
67
+ end
68
+
69
+ it 'ignores arrays' do
70
+ expect(subject.array_object.first).to include('mixedCaseChild' => 'exists')
71
+ end
72
+
73
+ context ':stop_at option' do
74
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data, stop_at: stop_at) }
75
+
76
+ context 'with symbol' do
77
+ let(:stop_at) { :hash_object }
78
+
79
+ it 'does not wrap the matching key' do
80
+ expect(subject.hash_object).to include('mixedCaseChild' => 'exists')
81
+ end
82
+ end
83
+
84
+ context 'with string' do
85
+ let(:stop_at) { ['hashObject'] }
86
+
87
+ it 'does not wrap the matching key' do
88
+ expect(subject.hash_object).to include('mixedCaseChild' => 'exists')
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ context 'non standard mixedCaseData' do
95
+ let(:data) do
96
+ {
97
+ :symbol => 'aSymbolValue',
98
+ :snake_case_symbol => 'snake_case_symbolValue',
99
+ :mixedCaseSymbol => 'mixedCaseSymbolValue',
100
+ 'snake_case_string' => 'snake_case_stringValue',
101
+ 'mixedCaseString' => 'mixedCaseStringFirstChoiceValue',
102
+ :mixedCaseString => 'mixedCaseStringFallbackValue',
103
+ :CamelCaseSymbol => 'CamelCaseSymbolValue',
104
+ 'CamelCaseString' => 'camel_case_stringValue',
105
+ :lowercasesymbol => 'lowercasesymbolValue',
106
+ 'lowercasestring' => 'lowercasestringValue'
107
+ }
108
+ end
109
+ let(:unique_value) { random_str }
110
+
111
+ subject { Ably::Models::IdiomaticRubyWrapper.new(data) }
112
+
113
+ {
114
+ :symbol => 'aSymbolValue',
115
+ :snake_case_symbol => 'snake_case_symbolValue',
116
+ :mixed_case_symbol => 'mixedCaseSymbolValue',
117
+ :snake_case_string => 'snake_case_stringValue',
118
+ :mixed_case_string => 'mixedCaseStringFirstChoiceValue',
119
+ :camel_case_symbol => 'CamelCaseSymbolValue',
120
+ :camel_case_string => 'camel_case_stringValue',
121
+ :lower_case_symbol => 'lowercasesymbolValue',
122
+ :lower_case_string => 'lowercasestringValue'
123
+ }.each do |symbol_accessor, expected_value|
124
+ context symbol_accessor do
125
+ it 'allows access to non conformant keys but prefers correct mixedCaseSyntax' do
126
+ expect(subject[symbol_accessor]).to eql(expected_value)
127
+ end
128
+
129
+ context 'updates' do
130
+ before do
131
+ subject[symbol_accessor] = unique_value
132
+ end
133
+
134
+ it 'returns the new value' do
135
+ expect(subject[symbol_accessor]).to eql(unique_value)
136
+ end
137
+
138
+ it 'returns the new value in the JSON' do
139
+ expect(subject.to_json).to include(unique_value)
140
+ expect(subject.to_json).to_not include(expected_value)
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ it 'returns nil for non existent keys' do
147
+ expect(subject[:non_existent_key]).to eql(nil)
148
+ end
149
+
150
+ context 'new keys' do
151
+ before do
152
+ subject[:new_key] = 'new_value'
153
+ end
154
+
155
+ it 'uses mixedCase' do
156
+ expect(subject.hash['newKey']).to eql('new_value')
157
+ expect(subject.new_key).to eql('new_value')
158
+ end
159
+ end
160
+ end
161
+
162
+ context 'acts like a duck' do
163
+ let(:parsed_json) { JSON.parse(subject.to_json) }
164
+ specify '#to_json returns JSON stringified' do
165
+ expect(subject.to_json).to eql(mixed_case_data.to_json)
166
+ end
167
+
168
+ specify '#to_json returns child JSON objects in the JSON stringified' do
169
+ expect(parsed_json['arrayObject']).to eql(mixed_case_data['arrayObject'])
170
+ end
171
+
172
+ context 'with snake case JSON' do
173
+ let(:subject) { Ably::Models::IdiomaticRubyWrapper.new('wrong_case' => 'will_be_corrected')}
174
+ specify '#to_json uses mixedCase for any non mixedCase keys' do
175
+ expect(parsed_json['wrongCase']).to eql('will_be_corrected')
176
+ end
177
+ end
178
+
179
+ context '#to_json with changes' do
180
+ before do
181
+ @original_mixed_case_data = mixed_case_data.to_json
182
+ subject[:mixed_case] = 'new_value'
183
+ end
184
+
185
+ it 'returns stringified JSON with changes' do
186
+ expect(subject.to_json).to_not eql(@original_mixed_case_data)
187
+ expect(subject.to_json).to match('new_value')
188
+ end
189
+ end
190
+
191
+ it 'returns correct size' do
192
+ expect(subject.size).to eql(mixed_case_data.size)
193
+ end
194
+
195
+ it 'supports Hash-like #keys' do
196
+ expect(subject.keys.length).to eql(mixed_case_data.keys.length)
197
+ end
198
+
199
+ it 'supports Hash-like #values' do
200
+ expect(subject.values.length).to eql(mixed_case_data.values.length)
201
+ end
202
+
203
+ it 'is Enumerable' do
204
+ expect(subject).to be_kind_of(Enumerable)
205
+ end
206
+
207
+ context 'iterable' do
208
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data, stop_at: [:hash_object, :array_object]) }
209
+
210
+ let(:expected_keys) { [:mixed_case, :simple, :hash_object, :array_object] }
211
+ let(:expected_vals) { mixed_case_data.map { |k,v| v } }
212
+
213
+ it 'yields key value pairs' do
214
+ expect(subject.map { |k,v| k }).to eql(expected_keys)
215
+ expect(subject.map { |k,v| v }).to eql(expected_vals)
216
+ end
217
+
218
+ context '#each' do
219
+ it 'returns an enumerator' do
220
+ expect(subject.each).to be_a(Enumerator)
221
+ end
222
+
223
+ it 'yields key value pairs' do
224
+ emitted = {}
225
+ subject.each do |key, val|
226
+ emitted[key] = val
227
+ end
228
+ expect(emitted.keys).to eql(expected_keys)
229
+ expect(emitted.values).to eql(expected_vals)
230
+ end
231
+ end
232
+ end
233
+
234
+ context '#fetch' do
235
+ it 'fetches the key' do
236
+ expect(subject.fetch(:mixed_case)).to eql('true')
237
+ end
238
+
239
+ it 'raise an exception if key does not exist' do
240
+ expect { subject.fetch(:non_existent) }.to raise_error KeyError, /key not found: non_existent/
241
+ end
242
+
243
+ it 'allows a default value argument' do
244
+ expect(subject.fetch(:non_existent, 'default')).to eql('default')
245
+ end
246
+
247
+ it 'calls the block if key does not exist' do
248
+ expect(subject.fetch(:non_existent) { 'block_default' } ).to eql('block_default')
249
+ end
250
+ end
251
+
252
+ context '#==' do
253
+ let(:mixed_case_data) do
254
+ {
255
+ 'key' => 'value'
256
+ }
257
+ end
258
+ let(:presented_as_data) do
259
+ {
260
+ :key => 'value'
261
+ }
262
+ end
263
+ let(:invalid_match) do
264
+ {
265
+ :key => 'other value'
266
+ }
267
+ end
268
+ let(:other) { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data) }
269
+ let(:other_invalid) { Ably::Models::IdiomaticRubyWrapper.new(invalid_match) }
270
+
271
+ it 'presents itself as a symbolized version of the object' do
272
+ expect(subject).to eq(presented_as_data)
273
+ end
274
+
275
+ it 'returns false if different values to another Hash' do
276
+ expect(subject).to_not eq(invalid_match)
277
+ end
278
+
279
+ it 'compares with itself' do
280
+ expect(subject).to eq(other)
281
+ end
282
+
283
+ it 'returns false if different values to another IdiomaticRubyWrapper' do
284
+ expect(subject).to_not eq(other_invalid)
285
+ end
286
+
287
+ it 'returns false if comparing with a non Hash/IdiomaticRubyWrapper object' do
288
+ expect(subject).to_not eq(Object)
289
+ end
290
+ end
291
+
292
+ context '#to_hash' do
293
+ let(:mixed_case_data) do
294
+ {
295
+ 'key' => 'value',
296
+ 'childObject' => {
297
+ 'child' => true
298
+ }
299
+ }
300
+ end
301
+
302
+ it 'returns a hash' do
303
+ expect(subject.to_hash).to include(key: 'value')
304
+ end
305
+
306
+ it 'converts hashes within hashes' do
307
+ expect(subject.to_hash[:child_object]).to include(child: true)
308
+ end
309
+ end
310
+
311
+ context '#to_msgpack' do
312
+ let(:mixed_case_data) do
313
+ {
314
+ 'key' => 'value',
315
+ 'child' => {
316
+ 'with_attributes' => true
317
+ }
318
+ }
319
+ end
320
+ let(:msg_packed) { subject.to_msgpack }
321
+ let(:unpacked) { MessagePack.unpack(msg_packed) }
322
+
323
+ it 'returns a msgpack object' do
324
+ expect(unpacked).to include('key' => 'value')
325
+ expect(unpacked).to include('child' => { 'withAttributes' => true })
326
+ end
327
+ end
328
+
329
+ context '#dup' do
330
+ let(:mixed_case_data) do
331
+ {
332
+ 'key' => 'value'
333
+ }.freeze
334
+ end
335
+ let(:dupe) { subject.dup }
336
+
337
+ it 'returns a new object with the underlying JSON duped' do
338
+ expect(subject.hash).to be_frozen
339
+ expect(dupe.hash).to_not be_frozen
340
+ end
341
+
342
+ it 'returns a new IdiomaticRubyWrapper with the same underlying Hash object' do
343
+ expect(dupe).to be_a(Ably::Models::IdiomaticRubyWrapper)
344
+ expect(dupe.hash).to be_a(Hash)
345
+ expect(dupe.hash).to eql(mixed_case_data)
346
+ end
347
+ end
348
+ end
349
+ end