ably 0.7.6 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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