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,165 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest do
5
+ describe 'transport protocol' do
6
+ include Ably::Modules::Conversions
7
+
8
+ let(:client_options) { {} }
9
+ let(:client) do
10
+ Ably::Rest::Client.new(client_options.merge(api_key: 'appid.keyuid:keysecret'))
11
+ end
12
+
13
+ let(:now) { Time.now - 1000 }
14
+ let(:body_value) { [as_since_epoch(now)] }
15
+
16
+ before do
17
+ stub_request(:get, "#{client.endpoint}/time").
18
+ with(:headers => { 'Accept' => mime }).
19
+ to_return(:status => 200, :body => request_body, :headers => { 'Content-Type' => mime })
20
+ end
21
+
22
+ context 'when protocol is not defined it defaults to :msgpack' do
23
+ let(:client_options) { { } }
24
+ let(:mime) { 'application/x-msgpack' }
25
+ let(:request_body) { body_value.to_msgpack }
26
+
27
+ it 'uses MsgPack', :webmock do
28
+ expect(client.protocol).to eql(:msgpack)
29
+ expect(client.time).to be_within(1).of(now)
30
+ end
31
+ end
32
+
33
+ options = [
34
+ { protocol: :json },
35
+ { use_binary_protocol: false }
36
+ ].each do |client_option|
37
+
38
+ context "when option #{client_option} is used" do
39
+ let(:client_options) { client_option }
40
+ let(:mime) { 'application/json' }
41
+ let(:request_body) { body_value.to_json }
42
+
43
+ it 'uses JSON', :webmock do
44
+ expect(client.protocol).to eql(:json)
45
+ expect(client.time).to be_within(1).of(now)
46
+ end
47
+ end
48
+ end
49
+
50
+ options = [
51
+ { protocol: :msgpack },
52
+ { use_binary_protocol: true }
53
+ ].each do |client_option|
54
+
55
+ context "when option #{client_option} is used" do
56
+ let(:client_options) { client_option }
57
+ let(:mime) { 'application/x-msgpack' }
58
+ let(:request_body) { body_value.to_msgpack }
59
+
60
+ it 'uses MsgPack', :webmock do
61
+ expect(client.protocol).to eql(:msgpack)
62
+ expect(client.time).to be_within(1).of(now)
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ vary_by_protocol do
69
+ let(:client) do
70
+ Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
71
+ end
72
+
73
+ describe 'failed requests' do
74
+ context 'due to invalid Auth' do
75
+ it 'should raise an InvalidRequest exception with a valid error message and code' do
76
+ invalid_client = Ably::Rest::Client.new(api_key: 'appid.keyuid:keysecret', environment: environment)
77
+ expect { invalid_client.channel('test').publish('foo', 'choo') }.to raise_error do |error|
78
+ expect(error).to be_a(Ably::Exceptions::InvalidRequest)
79
+ expect(error.message).to match(/invalid credentials/)
80
+ expect(error.code).to eql(40100)
81
+ expect(error.status).to eql(401)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe 'server error with JSON error response body', :webmock do
87
+ let(:error_response) { '{ "error": { "statusCode": 500, "code": 50000, "message": "Internal error" } }' }
88
+
89
+ before do
90
+ stub_request(:get, "#{client.endpoint}/time").
91
+ to_return(:status => 500, :body => error_response, :headers => { 'Content-Type' => 'application/json' })
92
+ end
93
+
94
+ it 'should raise a ServerError exception' do
95
+ expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Internal error/)
96
+ end
97
+ end
98
+
99
+ describe '500 server error without a valid JSON response body', :webmock do
100
+ before do
101
+ stub_request(:get, "#{client.endpoint}/time").
102
+ to_return(:status => 500, :headers => { 'Content-Type' => 'application/json' })
103
+ end
104
+
105
+ it 'should raise a ServerError exception' do
106
+ expect { client.time }.to raise_error(Ably::Exceptions::ServerError, /Unknown/)
107
+ end
108
+ end
109
+ end
110
+
111
+ describe 'token authentication failures', :webmock do
112
+ let(:token_1) { { id: random_str } }
113
+ let(:token_2) { { id: random_str } }
114
+ let(:channel) { 'channelname' }
115
+
116
+ before do
117
+ @token_requests = 0
118
+ @publish_attempts = 0
119
+
120
+ stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").to_return do
121
+ @token_requests += 1
122
+ {
123
+ :body => { access_token: send("token_#{@token_requests}").merge(expires: Time.now.to_i + 3600) }.to_json,
124
+ :headers => { 'Content-Type' => 'application/json' }
125
+ }
126
+ end
127
+
128
+ stub_request(:post, "#{client.endpoint}/channels/#{channel}/publish").to_return do
129
+ @publish_attempts += 1
130
+ if [1, 3].include?(@publish_attempts)
131
+ { status: 201, :body => '[]', :headers => { 'Content-Type' => 'application/json' } }
132
+ else
133
+ raise Ably::Exceptions::InvalidRequest.new('Authentication failure', 401, 40140)
134
+ end
135
+ end
136
+ end
137
+
138
+ context 'when auth#token_renewable?' do
139
+ before do
140
+ client.auth.authorise
141
+ end
142
+
143
+ it 'should automatically reissue a token' do
144
+ client.channel(channel).publish('evt', 'msg')
145
+ expect(@publish_attempts).to eql(1)
146
+
147
+ client.channel(channel).publish('evt', 'msg')
148
+ expect(@publish_attempts).to eql(3)
149
+ expect(@token_requests).to eql(2)
150
+ end
151
+ end
152
+
153
+ context 'when NOT auth#token_renewable?' do
154
+ let(:client) { Ably::Rest::Client.new(token_id: 'token ID cannot be used to create a new token', environment: environment, protocol: protocol) }
155
+
156
+ it 'should raise an InvalidToken exception' do
157
+ client.channel(channel).publish('evt', 'msg')
158
+ expect(@publish_attempts).to eql(1)
159
+ expect { client.channel(channel).publish('evt', 'msg') }.to raise_error Ably::Exceptions::InvalidToken
160
+ expect(@token_requests).to eql(0)
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,134 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest::Channel do
5
+ include Ably::Modules::Conversions
6
+
7
+ vary_by_protocol do
8
+ let(:client) do
9
+ Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
10
+ end
11
+
12
+ describe '#publish' do
13
+ let(:channel) { client.channel('test') }
14
+ let(:event) { 'foo' }
15
+ let(:message) { 'woop!' }
16
+
17
+ it 'should publish the message adn return true indicating success' do
18
+ expect(channel.publish(event, message)).to eql(true)
19
+ end
20
+ end
21
+
22
+ describe '#history' do
23
+ let(:channel) { client.channel("persisted:#{random_str(4)}") }
24
+ let(:expected_history) do
25
+ [
26
+ { :name => 'test1', :data => 'foo' },
27
+ { :name => 'test2', :data => 'bar' },
28
+ { :name => 'test3', :data => 'baz' }
29
+ ]
30
+ end
31
+ let!(:before_published) { client.time }
32
+
33
+ before(:each) do
34
+ expected_history.each do |message|
35
+ channel.publish(message[:name], message[:data]) || raise('Unable to publish message')
36
+ end
37
+ end
38
+
39
+ it 'should return the current message history for the channel' do
40
+ actual_history = channel.history
41
+
42
+ expect(actual_history.size).to eql(3)
43
+
44
+ expected_history.each do |message|
45
+ message_name, message_data = message[:name], message[:data]
46
+ matching_message = actual_history.find { |message| message.name == message_name && message.data == message_data }
47
+ expect(matching_message).to be_a(Ably::Models::Message)
48
+ end
49
+ end
50
+
51
+ context 'message timestamps' do
52
+ it 'should all be after the messages were published' do
53
+ channel.history.each do |message|
54
+ expect(before_published.to_f).to be < message.timestamp.to_f
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'message IDs' do
60
+ it 'should be unique' do
61
+ message_ids = channel.history.map(&:id).compact
62
+ expect(message_ids.count).to eql(3)
63
+ expect(message_ids.uniq.count).to eql(3)
64
+ end
65
+ end
66
+
67
+ it 'should return paged history using the PaginatedResource model' do
68
+ page_1 = channel.history(limit: 1)
69
+ page_2 = page_1.next_page
70
+ page_3 = page_2.next_page
71
+
72
+ all_items = [page_1[0].id, page_2[0].id, page_3[0].id]
73
+ expect(all_items.uniq).to eql(all_items)
74
+
75
+ expect(page_1.size).to eql(1)
76
+ expect(page_1).to_not be_last_page
77
+ expect(page_1).to be_first_page
78
+
79
+ # Page 2
80
+ expect(page_2.size).to eql(1)
81
+ expect(page_2).to_not be_last_page
82
+ expect(page_2).to_not be_first_page
83
+
84
+ # Page 3
85
+ expect(page_3.size).to eql(1)
86
+ expect(page_3).to be_last_page
87
+ expect(page_3).to_not be_first_page
88
+ end
89
+ end
90
+
91
+ describe '#history option' do
92
+ let(:channel_name) { "persisted:#{random_str(4)}" }
93
+ let(:channel) { client.channel(channel_name) }
94
+ let(:endpoint) do
95
+ client.endpoint.tap do |client_end_point|
96
+ client_end_point.user = key_id
97
+ client_end_point.password = key_secret
98
+ end
99
+ end
100
+
101
+ [:start, :end].each do |option|
102
+ describe ":#{option}", :webmock do
103
+ let!(:history_stub) {
104
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{option}=#{milliseconds}").
105
+ to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
106
+ }
107
+
108
+ before do
109
+ channel.history(options)
110
+ end
111
+
112
+ context 'with milliseconds since epoch value' do
113
+ let(:milliseconds) { as_since_epoch(Time.now) }
114
+ let(:options) { { option => milliseconds } }
115
+
116
+ it 'uses this value in the history request' do
117
+ expect(history_stub).to have_been_requested
118
+ end
119
+ end
120
+
121
+ context 'with a Time object value' do
122
+ let(:time) { Time.now }
123
+ let(:milliseconds) { as_since_epoch(time) }
124
+ let(:options) { { option => time } }
125
+
126
+ it 'converts the value to milliseconds since epoch in the hisotry request' do
127
+ expect(history_stub).to have_been_requested
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest::Channels do
5
+ shared_examples 'a channel' do
6
+ it 'returns a channel object' do
7
+ expect(channel).to be_a Ably::Rest::Channel
8
+ expect(channel.name).to eql(channel_name)
9
+ end
10
+
11
+ it 'returns channel object and passes the provided options' do
12
+ expect(channel_with_options.options).to eql(options)
13
+ end
14
+ end
15
+
16
+ vary_by_protocol do
17
+ let(:client) do
18
+ Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
19
+ end
20
+ let(:channel_name) { random_str }
21
+ let(:options) { { key: 'value' } }
22
+
23
+ describe 'using shortcut method #channel on the client object' do
24
+ let(:channel) { client.channel(channel_name) }
25
+ let(:channel_with_options) { client.channel(channel_name, options) }
26
+ it_behaves_like 'a channel'
27
+ end
28
+
29
+ describe 'using #get method on client#channels' do
30
+ let(:channel) { client.channels.get(channel_name) }
31
+ let(:channel_with_options) { client.channels.get(channel_name, options) }
32
+ it_behaves_like 'a channel'
33
+ end
34
+
35
+ describe 'using undocumented array accessor [] method on client#channels' do
36
+ let(:channel) { client.channels[channel_name] }
37
+ let(:channel_with_options) { client.channels[channel_name, options] }
38
+ it_behaves_like 'a channel'
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,273 @@
1
+ # encoding: utf-8
2
+ require 'spec_helper'
3
+
4
+ describe Ably::Rest::Client do
5
+ vary_by_protocol do
6
+ let(:default_options) { { environment: environment, protocol: protocol } }
7
+ let(:client_options) { default_options }
8
+
9
+ let(:client) { Ably::Rest::Client.new(client_options) }
10
+
11
+ connection_retry = Ably::Rest::Client::CONNECTION_RETRY
12
+
13
+ context '#initialize' do
14
+ let(:client_id) { random_str }
15
+ let(:token_request) { client.auth.create_token_request(key_id: key_id, key_secret: key_secret, client_id: client_id) }
16
+
17
+ context 'with an auth block' do
18
+ let(:client) { Ably::Rest::Client.new(client_options) { token_request } }
19
+
20
+ it 'calls the block to get a new token' do
21
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
22
+ expect(client.auth.current_token.client_id).to eql(client_id)
23
+ end
24
+ end
25
+
26
+ context 'with an auth URL' do
27
+ let(:client_options) { default_options.merge(auth_url: token_request_url, auth_method: :get) }
28
+ let(:token_request_url) { 'http://get.token.request.com/' }
29
+
30
+ before do
31
+ allow(client.auth).to receive(:token_request_from_auth_url).with(token_request_url, :auth_method => :get).and_return(token_request)
32
+ end
33
+
34
+ it 'sends an HTTP request to the provided URL to get a new token' do
35
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
36
+ expect(client.auth.current_token.client_id).to eql(client_id)
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'using tokens' do
42
+ let(:client) do
43
+ Ably::Rest::Client.new(client_options) do
44
+ @request_index ||= 0
45
+ @request_index += 1
46
+ send("token_request_#{@request_index}")
47
+ end
48
+ end
49
+ let(:token_request_1) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
50
+ let(:token_request_2) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
51
+
52
+ context 'when expired' do
53
+ let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: Ably::Models::Token::TOKEN_EXPIRY_BUFFER } }
54
+
55
+ it 'creates a new token automatically when the old token expires' do
56
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
57
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
58
+
59
+ sleep 1
60
+
61
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
62
+ expect(client.auth.current_token.client_id).to eql(token_request_2['clientId'])
63
+ end
64
+ end
65
+
66
+ context 'when token has not expired' do
67
+ let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: 3600 } }
68
+
69
+ it 'reuses the existing token for every request' do
70
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
71
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
72
+
73
+ sleep 1
74
+
75
+ expect { client.channel('channel_name').publish('event', 'message') }.to_not change { client.auth.current_token }
76
+ expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
77
+ end
78
+ end
79
+ end
80
+
81
+ context 'connection transport' do
82
+ let(:client_options) { default_options.merge(api_key: api_key) }
83
+
84
+ context 'for default host' do
85
+ it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
86
+ expect(client.connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
87
+ end
88
+
89
+ it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
90
+ expect(client.connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
91
+ end
92
+ end
93
+
94
+ context 'for the fallback hosts' do
95
+ it "is configured to timeout connection opening in #{connection_retry.fetch(:single_request_open_timeout)} seconds" do
96
+ expect(client.fallback_connection.options.open_timeout).to eql(connection_retry.fetch(:single_request_open_timeout))
97
+ end
98
+
99
+ it "is configured to timeout connection requests in #{connection_retry.fetch(:single_request_timeout)} seconds" do
100
+ expect(client.fallback_connection.options.timeout).to eql(connection_retry.fetch(:single_request_timeout))
101
+ end
102
+ end
103
+ end
104
+
105
+ context 'fallback hosts', :webmock do
106
+ let(:path) { '/channels/test/publish' }
107
+ let(:publish_block) { proc { client.channel('test').publish('event', 'data') } }
108
+
109
+ context 'configured' do
110
+ let(:client_options) { default_options.merge(api_key: api_key) }
111
+
112
+ it 'should make connection attempts to A.ably-realtime.com, B.ably-realtime.com, C.ably-realtime.com, D.ably-realtime.com, E.ably-realtime.com' do
113
+ hosts = []
114
+ 5.times do
115
+ hosts << client.fallback_connection.host
116
+ end
117
+ expect(hosts).to match_array(%w(A.ably-realtime.com B.ably-realtime.com C.ably-realtime.com D.ably-realtime.com E.ably-realtime.com))
118
+ end
119
+ end
120
+
121
+ context 'when environment is NOT production' do
122
+ let(:client_options) { default_options.merge(environment: 'sandbox', api_key: api_key) }
123
+ let!(:default_host_request_stub) do
124
+ stub_request(:post, "https://#{api_key}@#{environment}-#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
125
+ raise Faraday::TimeoutError.new('timeout error message')
126
+ end
127
+ end
128
+
129
+ it 'does not retry failed requests with fallback hosts when there is a connection error' do
130
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
131
+ end
132
+ end
133
+
134
+ context 'when environment is production' do
135
+ let(:custom_hosts) { %w(A.ably-realtime.com B.ably-realtime.com) }
136
+ let(:max_attempts) { 2 }
137
+ let(:cumulative_timeout) { 0.5 }
138
+ let(:client_options) { default_options.merge(environment: nil, api_key: api_key) }
139
+
140
+ before do
141
+ stub_const 'Ably::FALLBACK_HOSTS', custom_hosts
142
+ stub_const 'Ably::Rest::Client::CONNECTION_RETRY', {
143
+ single_request_open_timeout: 4,
144
+ single_request_timeout: 15,
145
+ cumulative_request_open_timeout: cumulative_timeout,
146
+ max_retry_attempts: max_attempts
147
+ }
148
+ end
149
+
150
+ let!(:first_fallback_request_stub) do
151
+ stub_request(:post, "https://#{api_key}@#{custom_hosts[0]}#{path}").to_return do
152
+ raise Faraday::SSLError.new('ssl error message')
153
+ end
154
+ end
155
+
156
+ let!(:second_fallback_request_stub) do
157
+ stub_request(:post, "https://#{api_key}@#{custom_hosts[1]}#{path}").to_return do
158
+ raise Faraday::SSLError.new('ssl error message')
159
+ end
160
+ end
161
+
162
+ context 'and connection times out' do
163
+ let!(:default_host_request_stub) do
164
+ stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
165
+ raise Faraday::TimeoutError.new('timeout error message')
166
+ end
167
+ end
168
+
169
+ it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
170
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
171
+ expect(default_host_request_stub).to have_been_requested
172
+ expect(first_fallback_request_stub).to have_been_requested
173
+ expect(second_fallback_request_stub).to have_been_requested
174
+ end
175
+
176
+ context "and the total request time exeeds #{connection_retry[:cumulative_request_open_timeout]} seconds" do
177
+ let!(:default_host_request_stub) do
178
+ stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
179
+ sleep cumulative_timeout * 1.5
180
+ raise Faraday::TimeoutError.new('timeout error message')
181
+ end
182
+ end
183
+
184
+ it 'makes no further attempts to any fallback hosts' do
185
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionTimeoutError
186
+ expect(default_host_request_stub).to have_been_requested
187
+ expect(first_fallback_request_stub).to_not have_been_requested
188
+ expect(second_fallback_request_stub).to_not have_been_requested
189
+ end
190
+ end
191
+ end
192
+
193
+ context 'and connection fails' do
194
+ let!(:default_host_request_stub) do
195
+ stub_request(:post, "https://#{api_key}@#{Ably::Rest::Client::DOMAIN}#{path}").to_return do
196
+ raise Faraday::ConnectionFailed.new('connection failure error message')
197
+ end
198
+ end
199
+
200
+ it "tries fallback hosts #{connection_retry[:max_retry_attempts]} times" do
201
+ expect { publish_block.call }.to raise_error Ably::Exceptions::ConnectionError, /ssl error message/
202
+ expect(default_host_request_stub).to have_been_requested
203
+ expect(first_fallback_request_stub).to have_been_requested
204
+ expect(second_fallback_request_stub).to have_been_requested
205
+ end
206
+ end
207
+ end
208
+ end
209
+
210
+ context 'with a custom host' do
211
+ let(:custom_host) { 'host.does.not.exist' }
212
+ let(:client_options) { default_options.merge(api_key: api_key, rest_host: custom_host) }
213
+ let(:capability) { { :foo => ["publish"] } }
214
+
215
+ context 'that does not exist' do
216
+ it 'fails immediately and raises a Faraday Error' do
217
+ expect { client.channel('test').publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionError
218
+ end
219
+
220
+ context 'fallback hosts', :webmock do
221
+ let(:path) { '/channels/test/publish' }
222
+
223
+ let!(:custom_host_request_stub) do
224
+ stub_request(:post, "https://#{api_key}@#{custom_host}#{path}").to_return do
225
+ raise Faraday::ConnectionFailed.new('connection failure error message')
226
+ end
227
+ end
228
+
229
+ before do
230
+ Ably::FALLBACK_HOSTS.each do |host|
231
+ stub_request(:post, "https://#{host}#{path}").to_return do
232
+ raise 'Fallbacks should not be used with custom hosts'
233
+ end
234
+ end
235
+ end
236
+
237
+ specify 'are never used' do
238
+ expect { client.channel('test').publish('event', 'data') }.to raise_error Ably::Exceptions::ConnectionError
239
+ expect(custom_host_request_stub).to have_been_requested
240
+ end
241
+ end
242
+ end
243
+
244
+ context 'that times out', :webmock do
245
+ let(:path) { '/keys/app_id.key_id/requestToken' }
246
+ let!(:custom_host_request_stub) do
247
+ stub_request(:post, "https://#{custom_host}#{path}").to_return do
248
+ raise Faraday::TimeoutError.new('timeout error message')
249
+ end
250
+ end
251
+
252
+ it 'fails immediately and raises a Faraday Error' do
253
+ expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
254
+ end
255
+
256
+ context 'fallback hosts' do
257
+ before do
258
+ Ably::FALLBACK_HOSTS.each do |host|
259
+ stub_request(:post, "https://#{host}#{path}").to_return do
260
+ raise 'Fallbacks should not be used with custom hosts'
261
+ end
262
+ end
263
+ end
264
+
265
+ specify 'are never used' do
266
+ expect { client.auth.request_token }.to raise_error Ably::Exceptions::ConnectionTimeoutError
267
+ expect(custom_host_request_stub).to have_been_requested
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+ end