ably 0.7.6 → 0.8.0

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -7
  3. data/SPEC.md +310 -269
  4. data/lib/ably/auth.rb +177 -127
  5. data/lib/ably/models/presence_message.rb +1 -1
  6. data/lib/ably/models/protocol_message.rb +1 -2
  7. data/lib/ably/models/token_details.rb +101 -0
  8. data/lib/ably/models/token_request.rb +108 -0
  9. data/lib/ably/modules/http_helpers.rb +1 -1
  10. data/lib/ably/realtime.rb +2 -6
  11. data/lib/ably/realtime/channel.rb +14 -8
  12. data/lib/ably/realtime/client.rb +2 -6
  13. data/lib/ably/realtime/connection.rb +4 -2
  14. data/lib/ably/rest.rb +2 -6
  15. data/lib/ably/rest/channel.rb +10 -6
  16. data/lib/ably/rest/client.rb +15 -16
  17. data/lib/ably/rest/presence.rb +12 -10
  18. data/lib/ably/version.rb +1 -1
  19. data/spec/acceptance/realtime/client_spec.rb +15 -15
  20. data/spec/acceptance/realtime/connection_failures_spec.rb +3 -3
  21. data/spec/acceptance/realtime/connection_spec.rb +9 -9
  22. data/spec/acceptance/rest/auth_spec.rb +248 -172
  23. data/spec/acceptance/rest/base_spec.rb +8 -6
  24. data/spec/acceptance/rest/channel_spec.rb +9 -2
  25. data/spec/acceptance/rest/client_spec.rb +21 -21
  26. data/spec/acceptance/rest/presence_spec.rb +12 -5
  27. data/spec/acceptance/rest/stats_spec.rb +4 -4
  28. data/spec/rspec_config.rb +3 -2
  29. data/spec/shared/client_initializer_behaviour.rb +21 -24
  30. data/spec/support/api_helper.rb +3 -3
  31. data/spec/support/test_app.rb +9 -9
  32. data/spec/unit/auth_spec.rb +17 -0
  33. data/spec/unit/models/token_details_spec.rb +111 -0
  34. data/spec/unit/models/token_request_spec.rb +110 -0
  35. data/spec/unit/rest/client_spec.rb +1 -1
  36. metadata +8 -5
  37. data/lib/ably/models/token.rb +0 -74
  38. data/spec/unit/models/token_spec.rb +0 -86
@@ -109,18 +109,18 @@ describe Ably::Rest do
109
109
  end
110
110
 
111
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' }
112
+ let(:token_1) { { token: random_str } }
113
+ let(:token_2) { { token: random_str } }
114
+ let(:channel) { random_str }
115
115
 
116
116
  before do
117
117
  @token_requests = 0
118
118
  @publish_attempts = 0
119
119
 
120
- stub_request(:post, "#{client.endpoint}/keys/#{key_id}/requestToken").to_return do
120
+ stub_request(:post, "#{client.endpoint}/keys/#{key_name}/requestToken").to_return do
121
121
  @token_requests += 1
122
122
  {
123
- :body => { access_token: send("token_#{@token_requests}").merge(expires: Time.now.to_i + 3600) }.to_json,
123
+ :body => public_send("token_#{@token_requests}").merge(expires: (Time.now.to_i + 60) * 1000).to_json,
124
124
  :headers => { 'Content-Type' => 'application/json' }
125
125
  }
126
126
  end
@@ -143,7 +143,9 @@ describe Ably::Rest do
143
143
  it 'should automatically reissue a token' do
144
144
  client.channel(channel).publish('evt', 'msg')
145
145
  expect(@publish_attempts).to eql(1)
146
+ expect(@token_requests).to eql(1)
146
147
 
148
+ # Triggers an authentication 401 failure which should automatically request a new token
147
149
  client.channel(channel).publish('evt', 'msg')
148
150
  expect(@publish_attempts).to eql(3)
149
151
  expect(@token_requests).to eql(2)
@@ -151,7 +153,7 @@ describe Ably::Rest do
151
153
  end
152
154
 
153
155
  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) }
156
+ let(:client) { Ably::Rest::Client.new(token: 'token ID cannot be used to create a new token', environment: environment, protocol: protocol) }
155
157
 
156
158
  it 'should raise an InvalidToken exception' do
157
159
  client.channel(channel).publish('evt', 'msg')
@@ -93,15 +93,22 @@ describe Ably::Rest::Channel do
93
93
  let(:channel) { client.channel(channel_name) }
94
94
  let(:endpoint) do
95
95
  client.endpoint.tap do |client_end_point|
96
- client_end_point.user = key_id
96
+ client_end_point.user = key_name
97
97
  client_end_point.password = key_secret
98
98
  end
99
99
  end
100
+ let(:default_options) do
101
+ {
102
+ direction: :backwards,
103
+ limit: 100
104
+ }
105
+ end
100
106
 
101
107
  [:start, :end].each do |option|
102
108
  describe ":#{option}", :webmock do
103
109
  let!(:history_stub) {
104
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{option}=#{milliseconds}").
110
+ query_params = default_options.merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
111
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/messages?#{query_params}").
105
112
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
106
113
  }
107
114
 
@@ -12,14 +12,14 @@ describe Ably::Rest::Client do
12
12
 
13
13
  context '#initialize' do
14
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) }
15
+ let(:token_request) { client.auth.create_token_request(key_name: key_name, key_secret: key_secret, client_id: client_id) }
16
16
 
17
- context 'with an auth block' do
18
- let(:client) { Ably::Rest::Client.new(client_options) { token_request } }
17
+ context 'with a :auth_callback Proc' do
18
+ let(:client) { Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new { token_request })) }
19
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)
20
+ it 'calls the auth Proc to get a new token' do
21
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
22
+ expect(client.auth.current_token_details.client_id).to eql(client_id)
23
23
  end
24
24
  end
25
25
 
@@ -32,19 +32,19 @@ describe Ably::Rest::Client do
32
32
  end
33
33
 
34
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)
35
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
36
+ expect(client.auth.current_token_details.client_id).to eql(client_id)
37
37
  end
38
38
  end
39
39
  end
40
40
 
41
41
  context 'using tokens' do
42
42
  let(:client) do
43
- Ably::Rest::Client.new(client_options) do
43
+ Ably::Rest::Client.new(client_options.merge(auth_callback: Proc.new do
44
44
  @request_index ||= 0
45
45
  @request_index += 1
46
46
  send("token_request_#{@request_index > 2 ? 'next' : @request_index}")
47
- end
47
+ end))
48
48
  end
49
49
  let(:token_request_1) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
50
50
  let(:token_request_2) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
@@ -53,30 +53,30 @@ describe Ably::Rest::Client do
53
53
  let(:token_request_next) { client.auth.create_token_request(token_request_options.merge(client_id: random_str)) }
54
54
 
55
55
  context 'when expired' do
56
- let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: Ably::Models::Token::TOKEN_EXPIRY_BUFFER } }
56
+ let(:token_request_options) { { key_name: key_name, key_secret: key_secret, ttl: Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER } }
57
57
 
58
58
  it 'creates a new token automatically when the old token expires' do
59
- expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
60
- expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
59
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
60
+ expect(client.auth.current_token_details.client_id).to eql(token_request_1.client_id)
61
61
 
62
62
  sleep 1
63
63
 
64
- expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
65
- expect(client.auth.current_token.client_id).to eql(token_request_2['clientId'])
64
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
65
+ expect(client.auth.current_token_details.client_id).to eql(token_request_2.client_id)
66
66
  end
67
67
  end
68
68
 
69
69
  context 'when token has not expired' do
70
- let(:token_request_options) { { key_id: key_id, key_secret: key_secret, ttl: 3600 } }
70
+ let(:token_request_options) { { key_name: key_name, key_secret: key_secret, ttl: 3600 } }
71
71
 
72
72
  it 'reuses the existing token for every request' do
73
- expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token }
74
- expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
73
+ expect { client.channel('channel_name').publish('event', 'message') }.to change { client.auth.current_token_details }
74
+ expect(client.auth.current_token_details.client_id).to eql(token_request_1.client_id)
75
75
 
76
76
  sleep 1
77
77
 
78
- expect { client.channel('channel_name').publish('event', 'message') }.to_not change { client.auth.current_token }
79
- expect(client.auth.current_token.client_id).to eql(token_request_1['clientId'])
78
+ expect { client.channel('channel_name').publish('event', 'message') }.to_not change { client.auth.current_token_details }
79
+ expect(client.auth.current_token_details.client_id).to eql(token_request_1.client_id)
80
80
  end
81
81
  end
82
82
  end
@@ -245,7 +245,7 @@ describe Ably::Rest::Client do
245
245
  end
246
246
 
247
247
  context 'that times out', :webmock do
248
- let(:path) { '/keys/app_id.key_id/requestToken' }
248
+ let(:path) { '/keys/app_id.key_name/requestToken' }
249
249
  let!(:custom_host_request_stub) do
250
250
  stub_request(:post, "https://#{custom_host}#{path}").to_return do
251
251
  raise Faraday::TimeoutError.new('timeout error message')
@@ -124,11 +124,18 @@ describe Ably::Rest::Presence do
124
124
  let(:client) do
125
125
  Ably::Rest::Client.new(key: "#{user}:#{secret}")
126
126
  end
127
+ let(:default_options) do
128
+ {
129
+ direction: :backwards,
130
+ limit: 100
131
+ }
132
+ end
127
133
 
128
134
  [:start, :end].each do |option|
129
135
  describe ":#{option}", :webmock do
130
136
  let!(:history_stub) {
131
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?#{option}=#{milliseconds}").
137
+ query_params = default_options.merge(option => milliseconds).map { |k, v| "#{k}=#{v}" }.join('&')
138
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?#{query_params}").
132
139
  to_return(:body => '{}', :headers => { 'Content-Type' => 'application/json' })
133
140
  }
134
141
 
@@ -225,7 +232,7 @@ describe Ably::Rest::Presence do
225
232
 
226
233
  context '#get' do
227
234
  let!(:get_stub) {
228
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
235
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence?limit=100").
229
236
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
230
237
  }
231
238
 
@@ -242,7 +249,7 @@ describe Ably::Rest::Presence do
242
249
 
243
250
  context '#history' do
244
251
  let!(:history_stub) {
245
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
252
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?direction=backwards&limit=100").
246
253
  to_return(:body => serialized_encoded_message, :headers => { 'Content-Type' => content_type })
247
254
  }
248
255
 
@@ -272,7 +279,7 @@ describe Ably::Rest::Presence do
272
279
  context '#get' do
273
280
  let(:client_options) { default_options.merge(log_level: :fatal) }
274
281
  let!(:get_stub) {
275
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence").
282
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence?limit=100").
276
283
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
277
284
  }
278
285
  let(:presence_message) { presence.get.items.first }
@@ -296,7 +303,7 @@ describe Ably::Rest::Presence do
296
303
  context '#history' do
297
304
  let(:client_options) { default_options.merge(log_level: :fatal) }
298
305
  let!(:history_stub) {
299
- stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history").
306
+ stub_request(:get, "#{endpoint}/channels/#{CGI.escape(channel_name)}/presence/history?direction=backwards&limit=100").
300
307
  to_return(:body => serialized_encoded_message_with_invalid_encoding, :headers => { 'Content-Type' => content_type })
301
308
  }
302
309
  let(:presence_message) { presence.history.items.first }
@@ -44,7 +44,7 @@ describe Ably::Rest::Client, '#stats' do
44
44
  let(:last_inbound_realtime_count) { STATS_FIXTURES.last[:inbound][:realtime][:messages][:count] }
45
45
 
46
46
  context 'with :from set to last interval and :limit set to 1' do
47
- let(:subject) { client.stats(start: as_since_epoch(LAST_INTERVAL), by: :minute, limit: 1) }
47
+ let(:subject) { client.stats(start: as_since_epoch(LAST_INTERVAL), end: LAST_INTERVAL, unit: :minute, limit: 1) }
48
48
  let(:stat) { subject.items.first }
49
49
 
50
50
  it 'retrieves only one stat' do
@@ -116,7 +116,7 @@ describe Ably::Rest::Client, '#stats' do
116
116
 
117
117
  context 'with :start set to first interval, :limit set to 1 and direction :forwards' do
118
118
  let(:first_interval) { LAST_INTERVAL - 120 }
119
- let(:subject) { client.stats(start: as_since_epoch(first_interval), by: :minute, direction: :forwards, limit: 1) }
119
+ let(:subject) { client.stats(start: as_since_epoch(first_interval), end: LAST_INTERVAL, unit: :minute, direction: :forwards, limit: 1) }
120
120
  let(:stat) { subject.items.first }
121
121
 
122
122
  it 'returns the first interval stats as stats are provided forwards from :start' do
@@ -133,7 +133,7 @@ describe Ably::Rest::Client, '#stats' do
133
133
  end
134
134
 
135
135
  context 'with :end set to last interval, :limit set to 1 and direction :backwards' do
136
- let(:subject) { client.stats(:end => as_since_epoch(LAST_INTERVAL), by: :minute, direction: :backwards, limit: 1) }
136
+ let(:subject) { client.stats(:end => as_since_epoch(LAST_INTERVAL), end: LAST_INTERVAL, unit: :minute, direction: :backwards, limit: 1) }
137
137
  let(:stat) { subject.items.first }
138
138
 
139
139
  it 'returns the 3rd interval stats first as stats are provided backwards from :end' do
@@ -151,7 +151,7 @@ describe Ably::Rest::Client, '#stats' do
151
151
 
152
152
  [:hour, :day, :month].each do |interval|
153
153
  context "by #{interval}" do
154
- let(:subject) { client.stats(start: as_since_epoch(LAST_INTERVAL), by: interval, direction: 'forwards', limit: 1) }
154
+ let(:subject) { client.stats(start: as_since_epoch(LAST_INTERVAL), end: LAST_INTERVAL, unit: interval, direction: 'forwards', limit: 1) }
155
155
  let(:stat) { subject.items.first }
156
156
  let(:aggregate_messages_count) do
157
157
  STATS_FIXTURES.inject(0) do |sum, fixture|
@@ -32,8 +32,9 @@ RSpec.configure do |config|
32
32
  config.before(:example, :webmock) do
33
33
  allow(TestApp).to receive(:instance).and_return(instance_double('TestApp',
34
34
  app_id: 'app_id',
35
- key_id: 'app_id.key_id',
36
- api_key: 'app_id.key_id:secret',
35
+ key_name: 'app_id.key_name',
36
+ key_secret: 'secret',
37
+ api_key: 'app_id.key_name:secret',
37
38
  environment: 'sandbox'
38
39
  ))
39
40
  WebMock.enable!
@@ -54,11 +54,11 @@ shared_examples 'a client initializer' do
54
54
  end
55
55
  end
56
56
 
57
- context 'key and key_id' do
58
- let(:client_options) { { key: 'appid.keyuid:keysecret', key_id: 'invalid' } }
57
+ context 'key and key_name' do
58
+ let(:client_options) { { key: 'appid.keyuid:keysecret', key_name: 'invalid' } }
59
59
 
60
60
  it 'raises an exception' do
61
- expect { subject }.to raise_error(ArgumentError, /key and key_id or key_secret are mutually exclusive/)
61
+ expect { subject }.to raise_error(ArgumentError, /key and key_name or key_secret are mutually exclusive/)
62
62
  end
63
63
  end
64
64
 
@@ -66,7 +66,7 @@ shared_examples 'a client initializer' do
66
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, /key and key_id or key_secret are mutually exclusive/)
69
+ expect { subject }.to raise_error(ArgumentError, /key and key_name or key_secret are mutually exclusive/)
70
70
  end
71
71
  end
72
72
 
@@ -89,19 +89,8 @@ shared_examples 'a client initializer' do
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
-
103
- context 'key_id and key_secret' do
104
- let(:client_options) { { key_id: 'id', key_secret: 'secret' } }
92
+ context 'key_name and key_secret', api_private: true do
93
+ let(:client_options) { { key_name: 'id', key_secret: 'secret' } }
105
94
 
106
95
  it 'constructs an key' do
107
96
  expect(subject.auth.key).to eql('id:secret')
@@ -115,8 +104,8 @@ shared_examples 'a client initializer' do
115
104
  expect(subject.auth.key).to eql(client_options)
116
105
  end
117
106
 
118
- it 'sets the key_id' do
119
- expect(subject.auth.key_id).to eql('app.key')
107
+ it 'sets the key_name' do
108
+ expect(subject.auth.key_name).to eql('app.key')
120
109
  end
121
110
 
122
111
  it 'sets the key_secret' do
@@ -127,16 +116,24 @@ shared_examples 'a client initializer' do
127
116
  context 'with a string token key instead of options hash' do
128
117
  let(:client_options) { 'app.kjhkasjhdsakdh127g7g1271' }
129
118
 
130
- it 'sets the token_id' do
131
- expect(subject.auth.token_id).to eql(client_options)
119
+ it 'sets the token' do
120
+ expect(subject.auth.token).to eql(client_options)
132
121
  end
133
122
  end
134
123
 
135
124
  context 'with token' do
136
- let(:client_options) { { token_id: 'token' } }
125
+ let(:client_options) { { token: 'token' } }
126
+
127
+ it 'sets the token' do
128
+ expect(subject.auth.token).to eql('token')
129
+ end
130
+ end
131
+
132
+ context 'with token_details' do
133
+ let(:client_options) { { token_details: Ably::Models::TokenDetails.new(token: 'token') } }
137
134
 
138
- it 'sets the token_id' do
139
- expect(subject.auth.token_id).to eql('token')
135
+ it 'sets the token' do
136
+ expect(subject.auth.token).to eql('token')
140
137
  end
141
138
  end
142
139
 
@@ -5,12 +5,12 @@ module ApiHelper
5
5
  TestApp.instance.app_id
6
6
  end
7
7
 
8
- def key_id
9
- TestApp.instance.key_id
8
+ def key_name
9
+ TestApp.instance.key_name
10
10
  end
11
11
 
12
12
  def key_secret
13
- api_key.split(':')[1]
13
+ TestApp.instance.key_secret
14
14
  end
15
15
 
16
16
  def api_key
@@ -27,31 +27,31 @@ class TestApp
27
27
  end
28
28
 
29
29
  def app_id
30
- @attributes["appId"]
30
+ @attributes.fetch('appId')
31
31
  end
32
32
 
33
33
  def key
34
- @attributes["keys"].first
34
+ @attributes.fetch('keys').first
35
35
  end
36
36
 
37
37
  def restricted_key
38
- @attributes["keys"][1]
38
+ @attributes.fetch('keys')[1]
39
39
  end
40
40
 
41
- def key_id
42
- "#{app_id}.#{key['id']}"
41
+ def key_name
42
+ key.fetch('keyName')
43
43
  end
44
44
 
45
- def key_value
46
- key['value']
45
+ def key_secret
46
+ key.fetch('keySecret')
47
47
  end
48
48
 
49
49
  def api_key
50
- "#{key_id}:#{key_value}"
50
+ key.fetch('keyStr')
51
51
  end
52
52
 
53
53
  def restricted_api_key
54
- "#{app_id}.#{restricted_key['id']}:#{restricted_key['value']}"
54
+ restricted_key.fetch('keyStr')
55
55
  end
56
56
 
57
57
  def delete
@@ -65,4 +65,21 @@ describe Ably::Auth do
65
65
  end
66
66
  end
67
67
  end
68
+
69
+ context 'defaults' do
70
+ let(:one_hour) { 60 * 60 }
71
+ let(:all_capabilities) { { "*" => ["*"] } }
72
+
73
+ it 'should default TTL to 1 hour' do
74
+ expect(Ably::Auth::TOKEN_DEFAULTS.fetch(:ttl)).to eql(one_hour)
75
+ end
76
+
77
+ it 'should default capability to all' do
78
+ expect(Ably::Auth::TOKEN_DEFAULTS.fetch(:capability)).to eql(all_capabilities)
79
+ end
80
+
81
+ it 'should only have defaults for :ttl and :capability' do
82
+ expect(Ably::Auth::TOKEN_DEFAULTS.keys).to contain_exactly(:ttl, :capability)
83
+ end
84
+ end
68
85
  end
@@ -0,0 +1,111 @@
1
+ require 'spec_helper'
2
+ require 'shared/model_behaviour'
3
+
4
+ describe Ably::Models::TokenDetails do
5
+ include Ably::Modules::Conversions
6
+
7
+ subject { Ably::Models::TokenDetails }
8
+
9
+ it_behaves_like 'a model', with_simple_attributes: %w(token key_name client_id) do
10
+ let(:model_args) { [] }
11
+ end
12
+
13
+
14
+ context 'attributes' do
15
+ let(:capability) { { "value" => random_str } }
16
+ let(:capability_str) { JSON.dump(capability) }
17
+
18
+ context '#capability' do
19
+ subject { Ably::Models::TokenDetails.new({ capability: capability_str }) }
20
+
21
+ it 'retrieves attribute :capability as parsed JSON' do
22
+ expect(subject.capability).to eql(capability)
23
+ end
24
+ end
25
+
26
+ { :issued => :issued, :expires => :expires }.each do |method_name, attribute|
27
+ let(:time) { Time.now }
28
+ context "##{method_name} with :#{method_name} option as milliseconds in constructor" do
29
+ subject { Ably::Models::TokenDetails.new({ attribute.to_sym => time.to_i * 1000 }) }
30
+
31
+ it "retrieves attribute :#{attribute} as Time" do
32
+ expect(subject.public_send(method_name)).to be_a(Time)
33
+ expect(subject.public_send(method_name).to_i).to eql(time.to_i)
34
+ end
35
+ end
36
+
37
+ context "##{method_name} with :#{method_name} option as a Time in constructor" do
38
+ subject { Ably::Models::TokenDetails.new({ attribute.to_sym => time }) }
39
+
40
+ it "retrieves attribute :#{attribute} as Time" do
41
+ expect(subject.public_send(method_name)).to be_a(Time)
42
+ expect(subject.public_send(method_name).to_i).to eql(time.to_i)
43
+ end
44
+ end
45
+
46
+ context "##{method_name} when converted to JSON" do
47
+ subject { Ably::Models::TokenDetails.new({ attribute.to_sym => time }) }
48
+
49
+ it 'is in milliseconds' do
50
+ expect(JSON.parse(JSON.dump(subject))[convert_to_mixed_case(attribute)]).to eql((time.to_f * 1000).round)
51
+ end
52
+ end
53
+ end
54
+
55
+ context '#expired?' do
56
+ let(:expire_time) { Time.now + Ably::Models::TokenDetails::TOKEN_EXPIRY_BUFFER }
57
+
58
+ context 'once grace period buffer has passed' do
59
+ subject { Ably::Models::TokenDetails.new(expires: expire_time - 1) }
60
+
61
+ it 'is true' do
62
+ expect(subject.expired?).to eql(true)
63
+ end
64
+ end
65
+
66
+ context 'within grace period buffer' do
67
+ subject { Ably::Models::TokenDetails.new(expires: expire_time + 1) }
68
+
69
+ it 'is false' do
70
+ expect(subject.expired?).to eql(false)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ context '==' do
77
+ let(:token_attributes) { { token: 'unique' } }
78
+
79
+ it 'is true when attributes are the same' do
80
+ new_token = -> { Ably::Models::TokenDetails.new(token_attributes) }
81
+ expect(new_token[]).to eq(new_token[])
82
+ end
83
+
84
+ it 'is false when attributes are not the same' do
85
+ expect(Ably::Models::TokenDetails.new(token: 1)).to_not eq(Ably::Models::TokenDetails.new(token: 2))
86
+ end
87
+
88
+ it 'is false when class type differs' do
89
+ expect(Ably::Models::TokenDetails.new(token: 1)).to_not eq(nil)
90
+ end
91
+ end
92
+
93
+ context 'TokenDetails conversion methods', :api_private do
94
+ context 'with a TokenDetails object' do
95
+ let(:token_details) { Ably::Models::TokenDetails.new(client_id: random_str) }
96
+
97
+ it 'returns the TokenDetails object' do
98
+ expect(Ably::Models::TokenDetails(token_details)).to eql(token_details)
99
+ end
100
+ end
101
+
102
+ context 'with a JSON object' do
103
+ let(:client_id) { random_str }
104
+ let(:token_details_json) { { client_id: client_id } }
105
+
106
+ it 'returns a new TokenDetails object from the JSON' do
107
+ expect(Ably::Models::TokenDetails(token_details_json).client_id).to eql(client_id)
108
+ end
109
+ end
110
+ end
111
+ end