ably 0.7.5 → 0.7.6

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 (63) hide show
  1. checksums.yaml +5 -13
  2. data/.gitignore +1 -0
  3. data/.gitmodules +3 -0
  4. data/README.md +46 -22
  5. data/SPEC.md +345 -240
  6. data/ably.gemspec +4 -2
  7. data/lib/ably/auth.rb +18 -14
  8. data/lib/ably/models/message.rb +1 -1
  9. data/lib/ably/models/paginated_resource.rb +31 -44
  10. data/lib/ably/models/presence_message.rb +1 -1
  11. data/lib/ably/models/stat.rb +67 -24
  12. data/lib/ably/models/stats_types.rb +131 -0
  13. data/lib/ably/modules/async_wrapper.rb +3 -2
  14. data/lib/ably/modules/message_emitter.rb +2 -2
  15. data/lib/ably/realtime.rb +1 -1
  16. data/lib/ably/realtime/channel.rb +24 -3
  17. data/lib/ably/realtime/channel/channel_manager.rb +1 -0
  18. data/lib/ably/realtime/client.rb +2 -2
  19. data/lib/ably/realtime/connection.rb +1 -1
  20. data/lib/ably/realtime/presence.rb +12 -1
  21. data/lib/ably/rest.rb +1 -1
  22. data/lib/ably/rest/channel.rb +4 -5
  23. data/lib/ably/rest/client.rb +5 -5
  24. data/lib/ably/rest/presence.rb +2 -2
  25. data/lib/ably/version.rb +1 -1
  26. data/spec/acceptance/realtime/channel_history_spec.rb +74 -23
  27. data/spec/acceptance/realtime/channel_spec.rb +3 -3
  28. data/spec/acceptance/realtime/client_spec.rb +3 -3
  29. data/spec/acceptance/realtime/connection_failures_spec.rb +2 -2
  30. data/spec/acceptance/realtime/connection_spec.rb +4 -4
  31. data/spec/acceptance/realtime/message_spec.rb +5 -5
  32. data/spec/acceptance/realtime/presence_history_spec.rb +56 -13
  33. data/spec/acceptance/realtime/presence_spec.rb +8 -8
  34. data/spec/acceptance/realtime/stats_spec.rb +1 -1
  35. data/spec/acceptance/realtime/time_spec.rb +1 -1
  36. data/spec/acceptance/rest/auth_spec.rb +31 -4
  37. data/spec/acceptance/rest/base_spec.rb +3 -3
  38. data/spec/acceptance/rest/channel_spec.rb +19 -19
  39. data/spec/acceptance/rest/channels_spec.rb +1 -1
  40. data/spec/acceptance/rest/client_spec.rb +9 -6
  41. data/spec/acceptance/rest/encoders_spec.rb +1 -1
  42. data/spec/acceptance/rest/message_spec.rb +10 -10
  43. data/spec/acceptance/rest/presence_spec.rb +81 -51
  44. data/spec/acceptance/rest/stats_spec.rb +46 -41
  45. data/spec/acceptance/rest/time_spec.rb +1 -1
  46. data/spec/shared/client_initializer_behaviour.rb +30 -19
  47. data/spec/spec_helper.rb +3 -0
  48. data/spec/support/markdown_spec_formatter.rb +1 -1
  49. data/spec/support/test_app.rb +11 -24
  50. data/spec/unit/auth_spec.rb +1 -1
  51. data/spec/unit/models/paginated_resource_spec.rb +81 -72
  52. data/spec/unit/models/stats_spec.rb +289 -0
  53. data/spec/unit/modules/async_wrapper_spec.rb +1 -1
  54. data/spec/unit/realtime/client_spec.rb +1 -1
  55. data/spec/unit/realtime/realtime_spec.rb +1 -1
  56. data/spec/unit/rest/channel_spec.rb +1 -1
  57. data/spec/unit/rest/client_spec.rb +8 -8
  58. data/spec/unit/rest/rest_spec.rb +1 -1
  59. data/spec/unit/util/crypto_spec.rb +1 -1
  60. metadata +55 -43
  61. data/spec/resources/crypto-data-128.json +0 -56
  62. data/spec/resources/crypto-data-256.json +0 -56
  63. data/spec/unit/models/stat_spec.rb +0 -113
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  describe Ably::Rest::Client, '#time' do
4
4
  vary_by_protocol do
5
5
  let(:client) do
6
- Ably::Rest::Client.new(api_key: api_key, environment: environment, protocol: protocol)
6
+ Ably::Rest::Client.new(key: api_key, environment: environment, protocol: protocol)
7
7
  end
8
8
 
9
9
  describe 'fetching the service time' do
@@ -26,7 +26,7 @@ shared_examples 'a client initializer' do
26
26
  let(:client_options) { Hash.new }
27
27
 
28
28
  it 'raises an exception' do
29
- expect { subject }.to raise_error(ArgumentError, /api_key is missing/)
29
+ expect { subject }.to raise_error(ArgumentError, /key is missing/)
30
30
  end
31
31
  end
32
32
 
@@ -38,35 +38,35 @@ shared_examples 'a client initializer' do
38
38
  end
39
39
  end
40
40
 
41
- context 'api_key: "invalid"' do
42
- let(:client_options) { { api_key: 'invalid' } }
41
+ context 'key: "invalid"' do
42
+ let(:client_options) { { key: 'invalid' } }
43
43
 
44
44
  it 'raises an exception' do
45
- expect { subject }.to raise_error(ArgumentError, /api_key is invalid/)
45
+ expect { subject }.to raise_error(ArgumentError, /key is invalid/)
46
46
  end
47
47
  end
48
48
 
49
- context 'api_key: "invalid:asdad"' do
50
- let(:client_options) { { api_key: 'invalid:asdad' } }
49
+ context 'key: "invalid:asdad"' do
50
+ let(:client_options) { { key: 'invalid:asdad' } }
51
51
 
52
52
  it 'raises an exception' do
53
- expect { subject }.to raise_error(ArgumentError, /api_key is invalid/)
53
+ expect { subject }.to raise_error(ArgumentError, /key is invalid/)
54
54
  end
55
55
  end
56
56
 
57
- context 'api_key and key_id' do
58
- let(:client_options) { { api_key: 'appid.keyuid:keysecret', key_id: 'invalid' } }
57
+ context 'key and key_id' do
58
+ let(:client_options) { { key: 'appid.keyuid:keysecret', key_id: 'invalid' } }
59
59
 
60
60
  it 'raises an exception' do
61
- expect { subject }.to raise_error(ArgumentError, /api_key and key_id or key_secret are mutually exclusive/)
61
+ expect { subject }.to raise_error(ArgumentError, /key and key_id or key_secret are mutually exclusive/)
62
62
  end
63
63
  end
64
64
 
65
- context 'api_key and key_secret' do
66
- let(:client_options) { { api_key: 'appid.keyuid:keysecret', key_secret: 'invalid' } }
65
+ context 'key and key_secret' do
66
+ let(:client_options) { { key: 'appid.keyuid:keysecret', key_secret: 'invalid' } }
67
67
 
68
68
  it 'raises an exception' do
69
- expect { subject }.to raise_error(ArgumentError, /api_key and key_id or key_secret are mutually exclusive/)
69
+ expect { subject }.to raise_error(ArgumentError, /key and key_id or key_secret are mutually exclusive/)
70
70
  end
71
71
  end
72
72
 
@@ -80,28 +80,39 @@ shared_examples 'a client initializer' do
80
80
  end
81
81
 
82
82
  context 'with valid arguments' do
83
- let(:default_options) { { api_key: 'appid.keyuid:keysecret' } }
83
+ let(:default_options) { { key: 'appid.keyuid:keysecret' } }
84
84
  let(:client_options) { default_options }
85
85
 
86
- context 'api_key only' do
86
+ context 'key only' do
87
87
  it 'connects to the Ably service' do
88
88
  expect { subject }.to_not raise_error
89
89
  end
90
90
  end
91
91
 
92
+ context 'with legacy :api_key only' do
93
+ let(:default_options) { { api_key: 'api_key_id.keyuid:keysecret' } }
94
+ it 'connects to the Ably service' do
95
+ expect { subject }.to_not raise_error
96
+ end
97
+
98
+ it 'sets the Auth#key' do
99
+ expect(subject.auth.key).to eql('api_key_id.keyuid:keysecret')
100
+ end
101
+ end
102
+
92
103
  context 'key_id and key_secret' do
93
104
  let(:client_options) { { key_id: 'id', key_secret: 'secret' } }
94
105
 
95
- it 'constructs an api_key' do
96
- expect(subject.auth.api_key).to eql('id:secret')
106
+ it 'constructs an key' do
107
+ expect(subject.auth.key).to eql('id:secret')
97
108
  end
98
109
  end
99
110
 
100
111
  context 'with a string key instead of options hash' do
101
112
  let(:client_options) { 'app.key:secret' }
102
113
 
103
- it 'sets the api_key' do
104
- expect(subject.auth.api_key).to eql(client_options)
114
+ it 'sets the key' do
115
+ expect(subject.auth.key).to eql(client_options)
105
116
  end
106
117
 
107
118
  it 'sets the key_id' do
@@ -4,6 +4,9 @@ def console(message)
4
4
  puts "\033[31m[#{Time.now.strftime('%H:%M:%S.%L')}]\033[0m \033[33m#{message}\033[0m"
5
5
  end
6
6
 
7
+ require 'coveralls'
8
+ Coveralls.wear!
9
+
7
10
  require 'webmock/rspec'
8
11
 
9
12
  require 'ably'
@@ -24,7 +24,7 @@ module Ably
24
24
  def start(notification)
25
25
  puts "\n\e[33m --> Creating SPEC.md <--\e[0m\n"
26
26
  scope = if defined?(Ably::Realtime)
27
- 'Real-time & REST'
27
+ 'Realtime & REST'
28
28
  else
29
29
  'REST'
30
30
  end
@@ -1,28 +1,15 @@
1
1
  require 'singleton'
2
2
 
3
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', 'data' => 'true' },
19
- { 'clientId' => 'client_int', 'data' => '24' },
20
- { 'clientId' => 'client_string', 'data' => 'This is a string clientData payload' },
21
- { 'clientId' => 'client_json', 'data' => '{ "test" => \'This is a JSONObject clientData payload\'}' }
22
- ]
23
- }
24
- ]
25
- }
4
+ TEST_RESOURCES_PATH = File.expand_path('../../../lib/submodules/ably-common/test-resources', __FILE__)
5
+
6
+ # App configuration for test app
7
+ # See https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json
8
+ APP_SPEC = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['post_apps']
9
+
10
+ # Cipher details used for client_encoded presence data in test app
11
+ # See https://github.com/ably/ably-common/blob/master/test-resources/test-app-setup.json
12
+ APP_SPEC_CIPHER = JSON.parse(File.read(File.join(TEST_RESOURCES_PATH, 'test-app-setup.json')))['cipher']
26
13
 
27
14
  # If an app has already been created and we need a new app, create a new test app
28
15
  # This is sometimes needed when a test needs to be isolated from any other tests
@@ -107,13 +94,13 @@ class TestApp
107
94
  end
108
95
 
109
96
  def create_test_stats(stats)
110
- client = Ably::Rest::Client.new(api_key: api_key, environment: environment)
97
+ client = Ably::Rest::Client.new(key: api_key, environment: environment)
111
98
  response = client.post('/stats', stats)
112
99
  raise "Could not create stats fixtures. Ably responded with status #{response.status}\n#{response.body}" unless (200..299).include?(response.status)
113
100
  end
114
101
 
115
102
  private
116
103
  def sandbox_client
117
- @sandbox_client ||= Ably::Rest::Client.new(api_key: 'app.key:secret', tls: true, environment: environment)
104
+ @sandbox_client ||= Ably::Rest::Client.new(key: 'app.key:secret', tls: true, environment: environment)
118
105
  end
119
106
  end
@@ -4,7 +4,7 @@ require 'shared/protocol_msgbus_behaviour'
4
4
  describe Ably::Auth do
5
5
  let(:client) { double('client').as_null_object }
6
6
  let(:client_id) { nil }
7
- let(:options) { { api_key: 'appid.keyuid:keysecret', client_id: client_id } }
7
+ let(:options) { { key: 'appid.keyuid:keysecret', client_id: client_id } }
8
8
 
9
9
  subject do
10
10
  Ably::Auth.new(client, options)
@@ -5,7 +5,7 @@ describe Ably::Models::PaginatedResource do
5
5
  let(:paginated_resource_class) { Ably::Models::PaginatedResource }
6
6
  let(:headers) { Hash.new }
7
7
  let(:client) do
8
- instance_double('Ably::Rest::Client', logger: true).tap do |client|
8
+ instance_double('Ably::Rest::Client', logger: Ably::Models::NilLogger.new).tap do |client|
9
9
  allow(client).to receive(:get).and_return(http_response)
10
10
  end
11
11
  end
@@ -27,57 +27,54 @@ describe Ably::Models::PaginatedResource do
27
27
  let(:first_paged_request) { paginated_resource_class.new(http_response, full_url, client, paginated_resource_options) }
28
28
  subject { first_paged_request }
29
29
 
30
- it 'returns correct length from body' do
31
- expect(subject.length).to eql(body.length)
32
- end
33
-
34
- it 'supports alias methods for length' do
35
- expect(subject.count).to eql(subject.length)
36
- expect(subject.size).to eql(subject.length)
37
- end
38
-
39
- it 'is Enumerable' do
40
- expect(subject).to be_kind_of(Enumerable)
41
- end
30
+ context '#items' do
31
+ it 'returns correct length from body' do
32
+ expect(subject.items.length).to eql(body.length)
33
+ end
42
34
 
43
- it 'is iterable' do
44
- expect(subject.map { |d| d }).to eql(body)
45
- end
35
+ it 'is Enumerable' do
36
+ expect(subject.items).to be_kind_of(Enumerable)
37
+ end
46
38
 
47
- context '#each' do
48
- it 'returns an enumerator' do
49
- expect(subject.each).to be_a(Enumerator)
39
+ it 'is iterable' do
40
+ expect(subject.items.map { |d| d }).to eql(body)
50
41
  end
51
42
 
52
- it 'yields each item' do
53
- items = []
54
- subject.each do |item|
55
- items << item
43
+ context '#each' do
44
+ it 'returns an enumerator' do
45
+ expect(subject.items.each).to be_a(Enumerator)
46
+ end
47
+
48
+ it 'yields each item' do
49
+ items = []
50
+ subject.items.each do |item|
51
+ items << item
52
+ end
53
+ expect(items).to eq(body)
56
54
  end
57
- expect(items).to eq(body)
58
55
  end
59
- end
60
56
 
61
- it 'provides [] accessor method' do
62
- expect(subject[0][:id]).to eql(body[0][:id])
63
- expect(subject[1][:id]).to eql(body[1][:id])
64
- expect(subject[2]).to be_nil
65
- end
57
+ it 'provides [] accessor method' do
58
+ expect(subject.items[0][:id]).to eql(body[0][:id])
59
+ expect(subject.items[1][:id]).to eql(body[1][:id])
60
+ expect(subject.items[2]).to be_nil
61
+ end
66
62
 
67
- specify '#first gets the first item in page' do
68
- expect(subject.first[:id]).to eql(body[0][:id])
69
- end
63
+ specify '#first gets the first item in page' do
64
+ expect(subject.items.first[:id]).to eql(body[0][:id])
65
+ end
70
66
 
71
- specify '#last gets the last item in page' do
72
- expect(subject.last[:id]).to eql(body[1][:id])
73
- end
67
+ specify '#last gets the last item in page' do
68
+ expect(subject.items.last[:id]).to eql(body[1][:id])
69
+ end
74
70
 
75
- context 'with coercion', :api_private do
76
- let(:paginated_resource_options) { { coerce_into: 'OpenStruct' } }
71
+ context 'with coercion', :api_private do
72
+ let(:paginated_resource_options) { { coerce_into: 'OpenStruct' } }
77
73
 
78
- it 'returns coerced objects' do
79
- expect(subject.first).to be_a(OpenStruct)
80
- expect(subject.first.id).to eql(body.first[:id])
74
+ it 'returns coerced objects' do
75
+ expect(subject.items.first).to be_a(OpenStruct)
76
+ expect(subject.items.first.id).to eql(body.first[:id])
77
+ end
81
78
  end
82
79
  end
83
80
 
@@ -90,7 +87,7 @@ describe Ably::Models::PaginatedResource do
90
87
  }
91
88
  end
92
89
  let(:paged_client) do
93
- instance_double('Ably::Rest::Client', logger: true).tap do |client|
90
+ instance_double('Ably::Rest::Client', logger: Ably::Models::NilLogger.new).tap do |client|
94
91
  allow(client).to receive(:get).and_return(http_response_page2)
95
92
  end
96
93
  end
@@ -116,15 +113,15 @@ describe Ably::Models::PaginatedResource do
116
113
  end
117
114
 
118
115
  it 'calls the block for each resource after retrieving the resources' do
119
- expect(subject[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
116
+ expect(subject.items[0][:added_attribute_from_block]).to eql("id:#{body[0][:id]}")
120
117
  end
121
118
 
122
119
  it 'calls the block for each resource on second page after retrieving the resources' do
123
- page_1_first_id = subject[0][:id]
124
- next_page = subject.next_page
120
+ page_1_first_id = subject.items[0][:id]
121
+ next_page = subject.next
125
122
 
126
- expect(next_page[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
127
- expect(next_page[0][:id]).to_not eql(page_1_first_id)
123
+ expect(next_page.items[0][:added_attribute_from_block]).to eql("id:#{body_page2[0][:id]}")
124
+ expect(next_page.items[0][:id]).to_not eql(page_1_first_id)
128
125
  end
129
126
  end
130
127
 
@@ -136,17 +133,17 @@ describe Ably::Models::PaginatedResource do
136
133
  paginated_resource_class.new(http_response, full_url, paged_client, async_blocking_operations: true)
137
134
  end
138
135
 
139
- context '#next_page' do
136
+ context '#next' do
140
137
  it 'returns a SafeDeferrable that catches exceptions in callbacks and logs them' do
141
138
  run_reactor do
142
- expect(subject.next_page).to be_a(Ably::Util::SafeDeferrable)
139
+ expect(subject.next).to be_a(Ably::Util::SafeDeferrable)
143
140
  stop_reactor
144
141
  end
145
142
  end
146
143
 
147
144
  it 'allows a success callback block to be added' do
148
145
  run_reactor do
149
- subject.next_page do |paginated_resource|
146
+ subject.next do |paginated_resource|
150
147
  expect(paginated_resource).to be_a(Ably::Models::PaginatedResource)
151
148
  stop_reactor
152
149
  end
@@ -154,11 +151,11 @@ describe Ably::Models::PaginatedResource do
154
151
  end
155
152
  end
156
153
 
157
- context '#first_page' do
154
+ context '#first' do
158
155
  it 'calls the errback callback when first page headers are missing' do
159
156
  run_reactor do
160
- subject.next_page do |paginated_resource|
161
- deferrable = subject.first_page
157
+ subject.next do |paginated_resource|
158
+ deferrable = subject.first
162
159
  deferrable.errback do |error|
163
160
  expect(error).to be_a(Ably::Exceptions::InvalidPageError)
164
161
  stop_reactor
@@ -173,23 +170,27 @@ describe Ably::Models::PaginatedResource do
173
170
 
174
171
  context 'with non paged http response' do
175
172
  it 'is the first page' do
176
- expect(subject).to be_first_page
173
+ expect(subject).to be_first
177
174
  end
178
175
 
179
176
  it 'is the last page' do
180
- expect(subject).to be_last_page
177
+ expect(subject).to be_last
178
+ end
179
+
180
+ it 'does not have next page' do
181
+ expect(subject).to_not have_next
181
182
  end
182
183
 
183
184
  it 'does not support pagination' do
184
185
  expect(subject.supports_pagination?).to_not eql(true)
185
186
  end
186
187
 
187
- it 'raises an exception when accessing next page' do
188
- expect { subject.next_page }.to raise_error Ably::Exceptions::InvalidPageError, /Paging header link next/
188
+ it 'returns nil when accessing next page' do
189
+ expect(subject.next).to be_nil
189
190
  end
190
191
 
191
- it 'raises an exception when accessing first page' do
192
- expect { subject.first_page }.to raise_error Ably::Exceptions::InvalidPageError, /Paging header link first/
192
+ it 'returns nil when accessing first page' do
193
+ expect(subject.first).to be_nil
193
194
  end
194
195
  end
195
196
 
@@ -207,11 +208,15 @@ describe Ably::Models::PaginatedResource do
207
208
  end
208
209
 
209
210
  it 'is the first page' do
210
- expect(subject).to be_first_page
211
+ expect(subject).to be_first
212
+ end
213
+
214
+ it 'has next page' do
215
+ expect(subject).to have_next
211
216
  end
212
217
 
213
218
  it 'is not the last page' do
214
- expect(subject).to_not be_last_page
219
+ expect(subject).to_not be_last
215
220
  end
216
221
 
217
222
  it 'supports pagination' do
@@ -236,7 +241,7 @@ describe Ably::Models::PaginatedResource do
236
241
  headers: next_headers
237
242
  })
238
243
  end
239
- let(:subject) { first_paged_request.next_page }
244
+ let(:subject) { first_paged_request.next }
240
245
 
241
246
  before do
242
247
  expect(client).to receive(:get).with("#{base_url}/history?index=1").and_return(next_http_response).once
@@ -247,39 +252,43 @@ describe Ably::Models::PaginatedResource do
247
252
  end
248
253
 
249
254
  it 'retrieves the next page of results' do
250
- expect(subject.length).to eql(next_body.length)
251
- expect(subject[0][:id]).to eql(next_body[0][:id])
255
+ expect(subject.items.length).to eql(next_body.length)
256
+ expect(subject.items[0][:id]).to eql(next_body[0][:id])
252
257
  end
253
258
 
254
259
  it 'is not the first page' do
255
- expect(subject).to_not be_first_page
260
+ expect(subject).to_not be_first
261
+ end
262
+
263
+ it 'does not have a next page' do
264
+ expect(subject).to_not have_next
256
265
  end
257
266
 
258
267
  it 'is the last page' do
259
- expect(subject).to be_last_page
268
+ expect(subject).to be_last
260
269
  end
261
270
 
262
- it 'raises an exception if trying to access the last page when it is the last page' do
263
- expect(subject).to be_last_page
264
- expect { subject.next_page }.to raise_error Ably::Exceptions::InvalidPageError, /There are no more pages/
271
+ it 'returns nil when trying to access the last page when it is the last page' do
272
+ expect(subject).to be_last
273
+ expect(subject.next).to be_nil
265
274
  end
266
275
 
267
276
  context 'and then first page' do
268
277
  before do
269
278
  expect(client).to receive(:get).with("#{base_url}/history?index=0").and_return(http_response).once
270
279
  end
271
- subject { first_paged_request.next_page.first_page }
280
+ subject { first_paged_request.next.first }
272
281
 
273
282
  it 'returns a PaginatedResource' do
274
283
  expect(subject).to be_a(paginated_resource_class)
275
284
  end
276
285
 
277
286
  it 'retrieves the first page of results' do
278
- expect(subject.length).to eql(body.length)
287
+ expect(subject.items.length).to eql(body.length)
279
288
  end
280
289
 
281
290
  it 'is the first page' do
282
- expect(subject).to be_first_page
291
+ expect(subject).to be_first
283
292
  end
284
293
  end
285
294
  end