ably-rest 0.9.1 → 0.9.2

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 (25) hide show
  1. checksums.yaml +4 -4
  2. data/lib/submodules/ably-ruby/lib/ably/exceptions.rb +3 -0
  3. data/lib/submodules/ably-ruby/lib/ably/models/message.rb +5 -0
  4. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base.rb +20 -4
  5. data/lib/submodules/ably-ruby/lib/ably/models/message_encoders/base64.rb +1 -1
  6. data/lib/submodules/ably-ruby/lib/ably/models/presence_message.rb +3 -0
  7. data/lib/submodules/ably-ruby/lib/ably/models/token_details.rb +11 -1
  8. data/lib/submodules/ably-ruby/lib/ably/models/token_request.rb +16 -6
  9. data/lib/submodules/ably-ruby/lib/ably/modules/encodeable.rb +51 -12
  10. data/lib/submodules/ably-ruby/lib/ably/modules/model_common.rb +17 -0
  11. data/lib/submodules/ably-ruby/lib/ably/realtime/channel.rb +8 -2
  12. data/lib/submodules/ably-ruby/lib/ably/realtime/presence.rb +4 -1
  13. data/lib/submodules/ably-ruby/lib/ably/realtime/presence/members_map.rb +4 -1
  14. data/lib/submodules/ably-ruby/lib/ably/rest/channel.rb +2 -2
  15. data/lib/submodules/ably-ruby/lib/ably/rest/client.rb +3 -13
  16. data/lib/submodules/ably-ruby/lib/ably/rest/presence.rb +1 -1
  17. data/lib/submodules/ably-ruby/lib/ably/version.rb +1 -1
  18. data/lib/submodules/ably-ruby/spec/acceptance/realtime/channel_spec.rb +2 -2
  19. data/lib/submodules/ably-ruby/spec/acceptance/realtime/presence_spec.rb +3 -3
  20. data/lib/submodules/ably-ruby/spec/unit/models/message_encoders/base64_spec.rb +2 -1
  21. data/lib/submodules/ably-ruby/spec/unit/models/message_spec.rb +121 -0
  22. data/lib/submodules/ably-ruby/spec/unit/models/presence_message_spec.rb +121 -0
  23. data/lib/submodules/ably-ruby/spec/unit/models/token_details_spec.rb +75 -0
  24. data/lib/submodules/ably-ruby/spec/unit/models/token_request_spec.rb +74 -0
  25. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 27ef716f8df3e3ded4ffffdcdbfb94c018a30e74
4
- data.tar.gz: 47d6e2d4b21491d1272c6c9d32b063e037d9c908
3
+ metadata.gz: 006c13b2408ff07c78159727a7e0bbd1a2eea9ab
4
+ data.tar.gz: 691848a554d61eb6c785cc7515f169e36bed8241
5
5
  SHA512:
6
- metadata.gz: fe7dc932158f9bba9d026eab21b38820ab80ed6811c01140e4472e88dacaaed2d59f6e3965125e42e3b912cb4f47892bac2ed9ea3c66969f332a81bb6717c7bc
7
- data.tar.gz: 2e826797df048e498f4c3083934df9219a42933d2286857fb1dd35c541730439bb4912220ddaa4032e7a2187fec5cf6e1cd5ae701262b89dde18a43f17e4b491
6
+ metadata.gz: 5ef3a684409bf54433d1c81b76da2555e6aa885dc547e4c11c8e4e94f24adf25c56cc84a2613f5d8b11c03294126a5b03b826a6a744820e1765b7eb4433eebf5
7
+ data.tar.gz: 562e4212388a59f3b53e699ff1933a1d02563b3c62af75370919c8dba10b984013e43a38bc73a33b364fe65952537d02d96eb77b30953e2c1df9d81382caf7fa
@@ -122,5 +122,8 @@ module Ably
122
122
  class ChannelInactive < BaseAblyException; end
123
123
 
124
124
  class IncompatibleClientId < BaseAblyException; end
125
+
126
+ # Token request has missing or invalid attributes
127
+ class InvalidTokenRequest < BaseAblyException; end
125
128
  end
126
129
  end
@@ -1,3 +1,5 @@
1
+ require 'ably/models/message_encoders/base'
2
+
1
3
  module Ably::Models
2
4
  # Convert messsage argument to a {Message} object and associate with a protocol message if provided
3
5
  #
@@ -43,6 +45,9 @@ module Ably::Models
43
45
  include Ably::Modules::ModelCommon
44
46
  include Ably::Modules::SafeDeferrable if defined?(Ably::Realtime)
45
47
 
48
+ # Statically register a default set of encoders for this class
49
+ Ably::Models::MessageEncoders.register_default_encoders self
50
+
46
51
  # {Message} initializer
47
52
  #
48
53
  # @param attributes [Hash] object with the underlying message detail key value attributes
@@ -13,10 +13,11 @@ module Ably::Models::MessageEncoders
13
13
  # Base interface for an Ably Encoder
14
14
  #
15
15
  class Base
16
- attr_reader :client
16
+ attr_reader :client, :options
17
17
 
18
- def initialize(client)
18
+ def initialize(client, options = {})
19
19
  @client = client
20
+ @options = options
20
21
  end
21
22
 
22
23
  # #encode is called once before a message is sent to Ably
@@ -94,11 +95,26 @@ module Ably::Models::MessageEncoders
94
95
  end
95
96
  end
96
97
 
97
- def self.register_default_encoders(client)
98
+ # @api private
99
+ def self.register_default_encoders(client, binary_protocol: false)
98
100
  client.register_encoder Ably::Models::MessageEncoders::Utf8
99
101
  client.register_encoder Ably::Models::MessageEncoders::Json
100
102
  client.register_encoder Ably::Models::MessageEncoders::Cipher
101
- client.register_encoder Ably::Models::MessageEncoders::Base64
103
+ client.register_encoder Ably::Models::MessageEncoders::Base64, binary_protocol: binary_protocol
104
+ end
105
+
106
+ # @api private
107
+ def self.encoder_from(encoder, options)
108
+ encoder_klass = if encoder.kind_of?(String)
109
+ encoder.split('::').inject(Kernel) do |base, klass_name|
110
+ base.public_send(:const_get, klass_name)
111
+ end
112
+ else
113
+ encoder
114
+ end
115
+
116
+ raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
117
+ encoder_klass.new(self, options)
102
118
  end
103
119
  end
104
120
 
@@ -34,7 +34,7 @@ module Ably::Models::MessageEncoders
34
34
  end
35
35
 
36
36
  def transport_protocol_text?
37
- !client.protocol_binary?
37
+ !options[:binary_protocol]
38
38
  end
39
39
  end
40
40
  end
@@ -52,6 +52,9 @@ module Ably::Models
52
52
  :update
53
53
  )
54
54
 
55
+ # Statically register a default set of encoders for this class
56
+ Ably::Models::MessageEncoders.register_default_encoders self
57
+
55
58
  # {PresenceMessage} initializer
56
59
  #
57
60
  # @param attributes [Hash] object with the underlying presence message key value attributes
@@ -73,7 +73,17 @@ module Ably::Models
73
73
  # @!attribute [r] capability
74
74
  # @return [Hash] Capabilities assigned to this token
75
75
  def capability
76
- JSON.parse(attributes.fetch(:capability)) if attributes.has_key?(:capability)
76
+ if attributes.has_key?(:capability)
77
+ capability_val = attributes.fetch(:capability)
78
+ case capability_val
79
+ when Hash
80
+ capability_val
81
+ when Ably::Models::IdiomaticRubyWrapper
82
+ capability_val.as_json
83
+ else
84
+ JSON.parse(attributes.fetch(:capability))
85
+ end
86
+ end
77
87
  end
78
88
 
79
89
  # @!attribute [r] client_id
@@ -42,7 +42,7 @@ module Ably::Models
42
42
  # @!attribute [r] key_name
43
43
  # @return [String] API key name of the key against which this request is made. An API key is made up of an API key name and secret delimited by a +:+
44
44
  def key_name
45
- attributes.fetch(:key_name)
45
+ attributes.fetch(:key_name) { raise Ably::Exceptions::InvalidTokenRequest, 'Key name is missing' }
46
46
  end
47
47
 
48
48
  # @!attribute [r] ttl
@@ -59,7 +59,16 @@ module Ably::Models
59
59
  # the capability of the returned token will be the intersection of
60
60
  # this capability with the capability of the issuing key.
61
61
  def capability
62
- JSON.parse(attributes.fetch(:capability))
62
+ capability_val = attributes.fetch(:capability) { raise Ably::Exceptions::InvalidTokenRequest, 'Capability is missing' }
63
+
64
+ case capability_val
65
+ when Hash
66
+ capability_val
67
+ when Ably::Models::IdiomaticRubyWrapper
68
+ capability_val.as_json
69
+ else
70
+ JSON.parse(attributes.fetch(:capability))
71
+ end
63
72
  end
64
73
 
65
74
  # @!attribute [r] client_id
@@ -75,7 +84,8 @@ module Ably::Models
75
84
  # token requests from being replayed.
76
85
  # Timestamp when sent to Ably is in milliseconds.
77
86
  def timestamp
78
- as_time_from_epoch(attributes.fetch(:timestamp), granularity: :ms)
87
+ timestamp_val = attributes.fetch(:timestamp) { raise Ably::Exceptions::InvalidTokenRequest, 'Timestamp is missing' }
88
+ as_time_from_epoch(timestamp_val, granularity: :ms)
79
89
  end
80
90
 
81
91
  # @!attribute [r] nonce
@@ -83,21 +93,21 @@ module Ably::Models
83
93
  # uniqueness of this request. Any subsequent request using the
84
94
  # same nonce will be rejected.
85
95
  def nonce
86
- attributes.fetch(:nonce)
96
+ attributes.fetch(:nonce) { raise Ably::Exceptions::InvalidTokenRequest, 'Nonce is missing' }
87
97
  end
88
98
 
89
99
  # @!attribute [r] mac
90
100
  # @return [String] the Message Authentication Code for this request. See the
91
101
  # {https://www.ably.io/documentation Ably Authentication documentation} for more details.
92
102
  def mac
93
- attributes.fetch(:mac)
103
+ attributes.fetch(:mac) { raise Ably::Exceptions::InvalidTokenRequest, 'MAC is missing' }
94
104
  end
95
105
 
96
106
  # Requests that the token is always persisted
97
107
  # @api private
98
108
  #
99
109
  def persisted
100
- attributes.fetch(:persisted)
110
+ attributes[:persisted]
101
111
  end
102
112
 
103
113
  # @!attribute [r] attributes
@@ -10,20 +10,60 @@ module Ably::Modules
10
10
  # - A #raw_hash_object attribute that returns the original hash object used to create this object
11
11
  #
12
12
  module Encodeable
13
+ def self.included(base)
14
+ base.extend(ClassMethods)
15
+ end
16
+
17
+ module ClassMethods
18
+ # Return a Message or Presence object from the encoded JSON-like object, using the optional channel options
19
+ # @param message_object [Hash] JSON-like object representation of an encoded message
20
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
21
+ # @yield [Ably::Exceptions::BaseAblyException] yields an Ably exception if decoding fails
22
+ # @return [Message,Presence]
23
+ def from_encoded(message_object, channel_options = {}, &error_block)
24
+ new(message_object).tap do |message|
25
+ message.decode(encoders, channel_options, &error_block)
26
+ end
27
+ end
28
+
29
+ # Return an Array of Message or Presence objects from the encoded Array of JSON-like objects, using the optional channel options
30
+ # @param message_objects [Array<Hash>] Array of JSON-like objects with encoded messages
31
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
32
+ # @return [Array<Message,Presence>]
33
+ def from_encoded_array(message_object_array, channel_options = {})
34
+ Array(message_object_array).map do |message_object|
35
+ from_encoded(message_object, channel_options)
36
+ end
37
+ end
38
+
39
+ # Register an encoder for this object
40
+ # @api private
41
+ def register_encoder(encoder, options = {})
42
+ encoders << Ably::Models::MessageEncoders.encoder_from(encoder, options)
43
+ end
44
+
45
+ private
46
+ def encoders
47
+ @encoders ||= []
48
+ end
49
+ end
50
+
13
51
  # Encode a message using the channel options and register encoders for the client
14
- # @param channel [Ably::Realtime::Channel]
52
+ # @param encoders [Array<Ably::Models::MessageEncoders::Base>] List of encoders to apply to the message
53
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
15
54
  # @return [void]
16
55
  # @api private
17
- def encode(channel)
18
- apply_encoders :encode, channel
56
+ def encode(encoders, channel_options, &error_block)
57
+ apply_encoders :encode, encoders, channel_options, &error_block
19
58
  end
20
59
 
21
60
  # Decode a message using the channel options and registered encoders for the client
22
- # @param channel [Ably::Realtime::Channel]
61
+ # @param encoders [Array<Ably::Models::MessageEncoders::Base>] List of encoders to apply to the message
62
+ # @param channel_options [Hash] Channel options, currently reserved for Encryption options
23
63
  # @return [void]
24
64
  # @api private
25
- def decode(channel)
26
- apply_encoders :decode, channel
65
+ def decode(encoders, channel_options, &error_block)
66
+ apply_encoders :decode, encoders, channel_options, &error_block
27
67
  end
28
68
 
29
69
  # The original encoding of this message when it was received as a raw message from the Ably service
@@ -44,7 +84,7 @@ module Ably::Modules
44
84
  end
45
85
  end
46
86
 
47
- def apply_encoders(method, channel)
87
+ def apply_encoders(method, encoders, channel_options, &error_callback)
48
88
  max_encoding_length = 512
49
89
  message_attributes = attributes.dup
50
90
 
@@ -54,16 +94,15 @@ module Ably::Modules
54
94
  end
55
95
 
56
96
  previous_encoding = message_attributes[:encoding]
57
- channel.client.encoders.each do |encoder|
58
- encoder.send method, message_attributes, channel.options
97
+ encoders.each do |encoder|
98
+ encoder.send method, message_attributes, channel_options
59
99
  end
60
100
  end until previous_encoding == message_attributes[:encoding]
61
101
 
62
102
  set_attributes_object message_attributes
63
103
  rescue Ably::Exceptions::CipherError => cipher_error
64
- if channel.respond_to?(:emit)
65
- channel.client.logger.error "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
66
- channel.emit :error, cipher_error
104
+ if block_given?
105
+ yield cipher_error, "Encoder error #{cipher_error.code} trying to #{method} message: #{cipher_error.message}"
67
106
  else
68
107
  raise cipher_error
69
108
  end
@@ -8,6 +8,10 @@ module Ably::Modules
8
8
  include Conversions
9
9
  include MessagePack
10
10
 
11
+ def self.included(base)
12
+ base.extend(ClassMethods)
13
+ end
14
+
11
15
  # Provide a normal Hash accessor to the underlying raw message object
12
16
  #
13
17
  # @return [Object]
@@ -38,6 +42,19 @@ module Ably::Modules
38
42
  attributes.hash
39
43
  end
40
44
 
45
+ module ClassMethods
46
+ # Return a new instance of this object using the provided JSON-like object or JSON string
47
+ # @param [Hash, String] JSON-like object or JSON string
48
+ # @return a new instance o this object
49
+ def from_json(json_like_object)
50
+ if json_like_object.kind_of?(String)
51
+ new(JSON.parse(json_like_object))
52
+ else
53
+ new(json_like_object)
54
+ end
55
+ end
56
+ end
57
+
41
58
  private
42
59
  def ensure_utf8_string_for(attribute, value)
43
60
  if value
@@ -299,7 +299,10 @@ module Ably
299
299
 
300
300
  def setup_event_handlers
301
301
  __incoming_msgbus__.subscribe(:message) do |message|
302
- message.decode self
302
+ message.decode(client.encoders, options) do |encode_error, error_message|
303
+ client.logger.error error_message
304
+ emit :error, encode_error
305
+ end
303
306
  emit_message message.name, message
304
307
  end
305
308
 
@@ -387,7 +390,10 @@ module Ably
387
390
 
388
391
  def create_message(message)
389
392
  Ably::Models::Message(message.dup).tap do |msg|
390
- msg.encode self
393
+ msg.encode(client.encoders, options) do |encode_error, error_message|
394
+ client.logger.error error_message
395
+ emit :error, encode_error
396
+ end
391
397
  end
392
398
  end
393
399
 
@@ -346,7 +346,10 @@ module Ably::Realtime
346
346
  }
347
347
 
348
348
  Ably::Models::PresenceMessage.new(model, logger: logger).tap do |presence_message|
349
- presence_message.encode self.channel
349
+ presence_message.encode(client.encoders, channel.options) do |encode_error, error_message|
350
+ client.logger.error error_message
351
+ emit :error, encode_error
352
+ end
350
353
  end
351
354
  end
352
355
 
@@ -165,7 +165,10 @@ module Ably::Realtime
165
165
 
166
166
  def setup_event_handlers
167
167
  presence.__incoming_msgbus__.subscribe(:presence, :sync) do |presence_message|
168
- presence_message.decode channel
168
+ presence_message.decode(client.encoders, channel.options) do |encode_error, error_message|
169
+ client.logger.error error_message
170
+ channel.emit :error, encode_error
171
+ end
169
172
  update_members_and_emit_events presence_message
170
173
  end
171
174
 
@@ -65,7 +65,7 @@ module Ably
65
65
 
66
66
  payload = messages.map do |message|
67
67
  Ably::Models::Message(message.dup).tap do |msg|
68
- msg.encode self
68
+ msg.encode client.encoders, options
69
69
 
70
70
  next if msg.client_id.nil?
71
71
  if msg.client_id == '*'
@@ -134,7 +134,7 @@ module Ably
134
134
  end
135
135
 
136
136
  def decode_message(message)
137
- message.decode self
137
+ message.decode client.encoders, options
138
138
  rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
139
139
  client.logger.error "Decoding Error on channel '#{name}', message event name '#{message.name}'. #{e.class.name}: #{e.message}"
140
140
  end
@@ -346,18 +346,8 @@ module Ably
346
346
  # @return [void]
347
347
  #
348
348
  # @api private
349
- def register_encoder(encoder)
350
- encoder_klass = if encoder.kind_of?(String)
351
- encoder.split('::').inject(Kernel) do |base, klass_name|
352
- base.public_send(:const_get, klass_name)
353
- end
354
- else
355
- encoder
356
- end
357
-
358
- raise "Encoder must inherit from `Ably::Models::MessageEncoders::Base`" unless encoder_klass.ancestors.include?(Ably::Models::MessageEncoders::Base)
359
-
360
- encoders << encoder_klass.new(self)
349
+ def register_encoder(encoder, options = {})
350
+ encoders << Ably::Models::MessageEncoders.encoder_from(encoder, options)
361
351
  end
362
352
 
363
353
  # @!attribute [r] protocol_binary?
@@ -535,7 +525,7 @@ module Ably
535
525
  end
536
526
 
537
527
  def initialize_default_encoders
538
- Ably::Models::MessageEncoders.register_default_encoders self
528
+ Ably::Models::MessageEncoders.register_default_encoders self, binary_protocol: protocol == :msgpack
539
529
  end
540
530
  end
541
531
  end
@@ -88,7 +88,7 @@ module Ably
88
88
  end
89
89
 
90
90
  def decode_message(presence_message)
91
- presence_message.decode channel
91
+ presence_message.decode client.encoders, channel.options
92
92
  rescue Ably::Exceptions::CipherError, Ably::Exceptions::EncoderError => e
93
93
  client.logger.error "Decoding Error on presence channel '#{channel.name}', presence message client_id '#{presence_message.client_id}'. #{e.class.name}: #{e.message}"
94
94
  end
@@ -1,5 +1,5 @@
1
1
  module Ably
2
- VERSION = '0.9.1'
2
+ VERSION = '0.9.2'
3
3
  PROTOCOL_VERSION = '0.9'
4
4
 
5
5
  # Allow a variant to be configured for all instances of this client library
@@ -367,11 +367,11 @@ describe Ably::Realtime::Channel, :event_machine do
367
367
  # All 3 messages should be batched into a single Protocol Message by the client library
368
368
  # message.id = "{protocol_message.id}:{protocol_message_index}"
369
369
  # Check that all messages share the same protocol_message.id
370
- message_id = messages.map { |msg| msg.id.split(':')[0] }
370
+ message_id = messages.map { |msg| msg.id.split(':')[0...-1].join(':') }
371
371
  expect(message_id.uniq.count).to eql(1)
372
372
 
373
373
  # Check that messages use index 0,1,2 in the ID
374
- message_indexes = messages.map { |msg| msg.id.split(':')[1] }
374
+ message_indexes = messages.map { |msg| msg.id.split(':').last }
375
375
  expect(message_indexes).to include("0", "1", "2")
376
376
  stop_reactor
377
377
  end
@@ -1226,11 +1226,11 @@ describe Ably::Realtime::Presence, :event_machine do
1226
1226
  let(:client_options) { default_options.merge(log_level: :none) }
1227
1227
 
1228
1228
  def connect_members_deferrables
1229
- (members_per_page * pages + 1).times.map do |index|
1229
+ (members_per_page * pages + 1).times.map do |mem_index|
1230
1230
  # rate limit to 10 per second
1231
1231
  EventMachine::DefaultDeferrable.new.tap do |deferrable|
1232
- EventMachine.add_timer(index / 10) do
1233
- presence_client_one.enter_client("client:#{index}").tap do |enter_deferrable|
1232
+ EventMachine.add_timer(mem_index/10) do
1233
+ presence_client_one.enter_client("client:#{mem_index}").tap do |enter_deferrable|
1234
1234
  enter_deferrable.callback { |*args| deferrable.succeed *args }
1235
1235
  enter_deferrable.errback { |*args| deferrable.fail *args }
1236
1236
  end
@@ -57,8 +57,9 @@ describe Ably::Models::MessageEncoders::Base64 do
57
57
 
58
58
  context '#encode' do
59
59
  context 'over binary transport' do
60
+ subject { Ably::Models::MessageEncoders::Base64.new(client, binary_protocol: true) }
61
+
60
62
  before do
61
- allow(client).to receive(:protocol_binary?).and_return(true)
62
63
  subject.encode message, {}
63
64
  end
64
65
 
@@ -379,4 +379,125 @@ describe Ably::Models::Message do
379
379
  end
380
380
  end
381
381
  end
382
+
383
+ context '#from_encoded (TM3)' do
384
+ context 'with no encoding' do
385
+ let(:message_data) do
386
+ { name: 'name', data: 'data-string' }
387
+ end
388
+ let(:from_encoded) { subject.from_encoded(message_data) }
389
+
390
+ it 'returns a message object' do
391
+ expect(from_encoded).to be_a(Ably::Models::Message)
392
+ expect(from_encoded.name).to eql('name')
393
+ expect(from_encoded.data).to eql('data-string')
394
+ expect(from_encoded.encoding).to be_nil
395
+ end
396
+
397
+ context 'with a block' do
398
+ it 'does not call the block' do
399
+ block_called = false
400
+ subject.from_encoded(message_data) do |exception, message|
401
+ block_called = true
402
+ end
403
+ expect(block_called).to be_falsey
404
+ end
405
+ end
406
+ end
407
+
408
+ context 'with an encoding' do
409
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
410
+ let(:message_data) do
411
+ { name: 'name', data: JSON.dump(hash_data), encoding: 'json' }
412
+ end
413
+ let(:from_encoded) { subject.from_encoded(message_data) }
414
+
415
+ it 'returns a message object' do
416
+ expect(from_encoded).to be_a(Ably::Models::Message)
417
+ expect(from_encoded.name).to eql('name')
418
+ expect(from_encoded.data).to eql(hash_data)
419
+ expect(from_encoded.encoding).to be_nil
420
+ end
421
+ end
422
+
423
+ context 'with a custom encoding' do
424
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
425
+ let(:message_data) do
426
+ { name: 'name', data: JSON.dump(hash_data), encoding: 'foo/json' }
427
+ end
428
+ let(:from_encoded) { subject.from_encoded(message_data) }
429
+
430
+ it 'returns a message object with the residual incompatible transforms left in the encoding property' do
431
+ expect(from_encoded).to be_a(Ably::Models::Message)
432
+ expect(from_encoded.name).to eql('name')
433
+ expect(from_encoded.data).to eql(hash_data)
434
+ expect(from_encoded.encoding).to eql('foo')
435
+ end
436
+ end
437
+
438
+ context 'with a Cipher encoding' do
439
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
440
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
441
+ let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
442
+ let(:payload) { random_str }
443
+ let(:message_data) do
444
+ { name: 'name', data: crypto.encrypt(payload), encoding: 'utf-8/cipher+aes-128-cbc' }
445
+ end
446
+ let(:channel_options) { { cipher: cipher_params } }
447
+ let(:from_encoded) { subject.from_encoded(message_data, channel_options) }
448
+
449
+ it 'returns a message object with the residual incompatible transforms left in the encoding property' do
450
+ expect(from_encoded).to be_a(Ably::Models::Message)
451
+ expect(from_encoded.name).to eql('name')
452
+ expect(from_encoded.data).to eql(payload)
453
+ expect(from_encoded.encoding).to be_nil
454
+ end
455
+ end
456
+
457
+ context 'with invalid Cipher encoding' do
458
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
459
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
460
+ let(:unencryped_payload) { random_str }
461
+ let(:message_data) do
462
+ { name: 'name', data: unencryped_payload, encoding: 'utf-8/cipher+aes-128-cbc' }
463
+ end
464
+ let(:channel_options) { { cipher: cipher_params } }
465
+
466
+ context 'without a block' do
467
+ it 'raises an exception' do
468
+ expect { subject.from_encoded(message_data, channel_options) }.to raise_exception(Ably::Exceptions::CipherError)
469
+ end
470
+ end
471
+
472
+ context 'with a block' do
473
+ it 'calls the block with the exception' do
474
+ block_called = false
475
+ subject.from_encoded(message_data, channel_options) do |exception, message|
476
+ expect(exception).to be_a(Ably::Exceptions::CipherError)
477
+ block_called = true
478
+ end
479
+ expect(block_called).to be_truthy
480
+ end
481
+ end
482
+ end
483
+ end
484
+
485
+ context '#from_encoded_array (TM3)' do
486
+ context 'with no encoding' do
487
+ let(:message_data) do
488
+ [{ name: 'name1', data: 'data-string' }, { name: 'name2', data: 'data-string' }]
489
+ end
490
+ let(:from_encoded) { subject.from_encoded_array(message_data) }
491
+
492
+ it 'returns an Array of message objects' do
493
+ first = from_encoded.first
494
+ expect(first).to be_a(Ably::Models::Message)
495
+ expect(first.name).to eql('name1')
496
+ expect(first.data).to eql('data-string')
497
+ expect(first.encoding).to be_nil
498
+ last = from_encoded.last
499
+ expect(last.name).to eql('name2')
500
+ end
501
+ end
502
+ end
382
503
  end
@@ -383,4 +383,125 @@ describe Ably::Models::PresenceMessage do
383
383
  end
384
384
  end
385
385
  end
386
+
387
+
388
+ context '#from_encoded (TP4)' do
389
+ context 'with no encoding' do
390
+ let(:message_data) do
391
+ { action: 2, data: 'data-string' }
392
+ end
393
+ let(:from_encoded) { subject.from_encoded(message_data) }
394
+
395
+ it 'returns a presence message object' do
396
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
397
+ expect(from_encoded.action).to eq(:enter)
398
+ expect(from_encoded.data).to eql('data-string')
399
+ expect(from_encoded.encoding).to be_nil
400
+ end
401
+
402
+ context 'with a block' do
403
+ it 'does not call the block' do
404
+ block_called = false
405
+ subject.from_encoded(message_data) do |exception, message|
406
+ block_called = true
407
+ end
408
+ expect(block_called).to be_falsey
409
+ end
410
+ end
411
+ end
412
+
413
+ context 'with an encoding' do
414
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
415
+ let(:message_data) do
416
+ { action: 'leave', data: JSON.dump(hash_data), encoding: 'json' }
417
+ end
418
+ let(:from_encoded) { subject.from_encoded(message_data) }
419
+
420
+ it 'returns a presence message object' do
421
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
422
+ expect(from_encoded.action).to eq(:leave)
423
+ expect(from_encoded.data).to eql(hash_data)
424
+ expect(from_encoded.encoding).to be_nil
425
+ end
426
+ end
427
+
428
+ context 'with a custom encoding' do
429
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
430
+ let(:message_data) do
431
+ { action: 1, data: JSON.dump(hash_data), encoding: 'foo/json' }
432
+ end
433
+ let(:from_encoded) { subject.from_encoded(message_data) }
434
+
435
+ it 'returns a presence message object with the residual incompatible transforms left in the encoding property' do
436
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
437
+ expect(from_encoded.action).to eq(1)
438
+ expect(from_encoded.data).to eql(hash_data)
439
+ expect(from_encoded.encoding).to eql('foo')
440
+ end
441
+ end
442
+
443
+ context 'with a Cipher encoding' do
444
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
445
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
446
+ let(:crypto) { Ably::Util::Crypto.new(cipher_params) }
447
+ let(:payload) { random_str }
448
+ let(:message_data) do
449
+ { action: 1, data: crypto.encrypt(payload), encoding: 'utf-8/cipher+aes-128-cbc' }
450
+ end
451
+ let(:channel_options) { { cipher: cipher_params } }
452
+ let(:from_encoded) { subject.from_encoded(message_data, channel_options) }
453
+
454
+ it 'returns a presence message object with the residual incompatible transforms left in the encoding property' do
455
+ expect(from_encoded).to be_a(Ably::Models::PresenceMessage)
456
+ expect(from_encoded.data).to eql(payload)
457
+ expect(from_encoded.encoding).to be_nil
458
+ end
459
+ end
460
+
461
+ context 'with invalid Cipher encoding' do
462
+ let(:hash_data) { { 'key' => 'value', 'key2' => 123 } }
463
+ let(:cipher_params) { { key: Ably::Util::Crypto.generate_random_key(128), algorithm: 'aes', mode: 'cbc', key_length: 128 } }
464
+ let(:unencryped_payload) { random_str }
465
+ let(:message_data) do
466
+ { action: 1, data: unencryped_payload, encoding: 'utf-8/cipher+aes-128-cbc' }
467
+ end
468
+ let(:channel_options) { { cipher: cipher_params } }
469
+
470
+ context 'without a block' do
471
+ it 'raises an exception' do
472
+ expect { subject.from_encoded(message_data, channel_options) }.to raise_exception(Ably::Exceptions::CipherError)
473
+ end
474
+ end
475
+
476
+ context 'with a block' do
477
+ it 'calls the block with the exception' do
478
+ block_called = false
479
+ subject.from_encoded(message_data, channel_options) do |exception, message|
480
+ expect(exception).to be_a(Ably::Exceptions::CipherError)
481
+ block_called = true
482
+ end
483
+ expect(block_called).to be_truthy
484
+ end
485
+ end
486
+ end
487
+ end
488
+
489
+ context '#from_encoded_array (TP4)' do
490
+ context 'with no encoding' do
491
+ let(:message_data) do
492
+ [{ action: 1, data: 'data-string' }, { action: 2, data: 'data-string' }]
493
+ end
494
+ let(:from_encoded) { subject.from_encoded_array(message_data) }
495
+
496
+ it 'returns an Array of presence message objects' do
497
+ first = from_encoded.first
498
+ expect(first).to be_a(Ably::Models::PresenceMessage)
499
+ expect(first.action).to eq(1)
500
+ expect(first.data).to eql('data-string')
501
+ expect(first.encoding).to be_nil
502
+ last = from_encoded.last
503
+ expect(last.action).to eq(:enter)
504
+ end
505
+ end
506
+ end
386
507
  end
@@ -146,4 +146,79 @@ describe Ably::Models::TokenDetails do
146
146
  end
147
147
  end
148
148
  end
149
+
150
+ context 'from_json (TD7)' do
151
+ let(:issued_time) { Time.now }
152
+ let(:expires_time) { Time.now + 24*60*60 }
153
+ let(:capabilities) { { '*' => ['publish'] } }
154
+
155
+ context 'with Ruby idiomatic Hash object' do
156
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
157
+
158
+ let(:token_details_object) do
159
+ {
160
+ token: 'val1',
161
+ key_name: 'val2',
162
+ issued: issued_time.to_i * 1000,
163
+ expires: expires_time.to_i * 1000,
164
+ capability: capabilities,
165
+ client_id: 'val3'
166
+ }
167
+ end
168
+
169
+ it 'returns a valid TokenDetails object' do
170
+ expect(subject.token).to eql('val1')
171
+ expect(subject.key_name).to eql('val2')
172
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
173
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
174
+ expect(subject.capability).to eql(capabilities)
175
+ expect(subject.client_id).to eql('val3')
176
+ end
177
+ end
178
+
179
+ context 'with JSON-like object' do
180
+ subject { Ably::Models::TokenDetails.from_json(token_details_object) }
181
+
182
+ let(:token_details_object) do
183
+ {
184
+ 'keyName' => 'val2',
185
+ 'issued' => issued_time.to_i * 1000,
186
+ 'expires' => expires_time.to_i * 1000,
187
+ 'capability' => JSON.dump(capabilities),
188
+ 'clientId' => 'val3'
189
+ }
190
+ end
191
+
192
+ it 'returns a valid TokenDetails object' do
193
+ expect(subject.token).to be_nil
194
+ expect(subject.key_name).to eql('val2')
195
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
196
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
197
+ expect(subject.capability).to eql(capabilities)
198
+ expect(subject.client_id).to eql('val3')
199
+ end
200
+ end
201
+
202
+ context 'with JSON string' do
203
+ subject { Ably::Models::TokenDetails.from_json(JSON.dump(token_details_object)) }
204
+
205
+ let(:token_details_object) do
206
+ {
207
+ 'keyName' => 'val2',
208
+ 'issued' => issued_time.to_i * 1000,
209
+ 'expires' => expires_time.to_i * 1000,
210
+ 'clientId' => 'val3'
211
+ }
212
+ end
213
+
214
+ it 'returns a valid TokenDetails object' do
215
+ expect(subject.token).to be_nil
216
+ expect(subject.key_name).to eql('val2')
217
+ expect(subject.issued.to_f).to be_within(1).of(issued_time.to_f)
218
+ expect(subject.expires.to_f).to be_within(1).of(expires_time.to_f)
219
+ expect(subject.capability).to be_nil
220
+ expect(subject.client_id).to eql('val3')
221
+ end
222
+ end
223
+ end
149
224
  end
@@ -107,4 +107,78 @@ describe Ably::Models::TokenRequest do
107
107
  end
108
108
  end
109
109
  end
110
+
111
+ context 'from_json (TE6)' do
112
+ let(:timestamp) { Time.now }
113
+ let(:capabilities) { { '*' => ['publish'] } }
114
+ let(:ttl_seconds) { 60 * 1000 }
115
+
116
+ context 'with Ruby idiomatic Hash object' do
117
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
118
+
119
+ let(:token_request_object) do
120
+ {
121
+ nonce: 'val1',
122
+ key_name: 'val2',
123
+ ttl: ttl_seconds * 1000,
124
+ timestamp: timestamp.to_i * 1000,
125
+ capability: capabilities,
126
+ client_id: 'val3'
127
+ }
128
+ end
129
+
130
+ it 'returns a valid TokenRequest object' do
131
+ expect(subject.nonce).to eql('val1')
132
+ expect(subject.key_name).to eql('val2')
133
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
134
+ expect(subject.ttl).to eql(ttl_seconds)
135
+ expect(subject.capability).to eql(capabilities)
136
+ expect(subject.client_id).to eql('val3')
137
+ end
138
+ end
139
+
140
+ context 'with JSON-like object' do
141
+ subject { Ably::Models::TokenRequest.from_json(token_request_object) }
142
+
143
+ let(:token_request_object) do
144
+ {
145
+ 'keyName' => 'val2',
146
+ 'ttl' => ttl_seconds * 1000,
147
+ 'timestamp' => timestamp.to_i * 1000,
148
+ 'clientId' => 'val3'
149
+ }
150
+ end
151
+
152
+ it 'returns a valid TokenRequest object' do
153
+ expect { subject.nonce }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
154
+ expect(subject.key_name).to eql('val2')
155
+ expect(subject.timestamp.to_f).to be_within(1).of(timestamp.to_f)
156
+ expect(subject.ttl).to eql(ttl_seconds)
157
+ expect { subject.capability }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
158
+ expect(subject.client_id).to eql('val3')
159
+ end
160
+ end
161
+
162
+ context 'with JSON string' do
163
+ subject { Ably::Models::TokenRequest.from_json(JSON.dump(token_request_object)) }
164
+
165
+ let(:token_request_object) do
166
+ {
167
+ 'nonce' => 'val1',
168
+ 'ttl' => ttl_seconds * 1000,
169
+ 'capability' => JSON.dump(capabilities),
170
+ 'clientId' => 'val3'
171
+ }
172
+ end
173
+
174
+ it 'returns a valid TokenRequest object' do
175
+ expect(subject.nonce).to eql('val1')
176
+ expect { subject.key_name }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
177
+ expect { subject.timestamp }.to raise_error(Ably::Exceptions::InvalidTokenRequest)
178
+ expect(subject.ttl).to eql(ttl_seconds)
179
+ expect(subject.capability).to eql(capabilities)
180
+ expect(subject.client_id).to eql('val3')
181
+ end
182
+ end
183
+ end
110
184
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ably-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthew O'Riordan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-10-02 00:00:00.000000000 Z
11
+ date: 2016-11-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday
@@ -405,7 +405,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
405
405
  version: '0'
406
406
  requirements: []
407
407
  rubyforge_project:
408
- rubygems_version: 2.5.1
408
+ rubygems_version: 2.4.6
409
409
  signing_key:
410
410
  specification_version: 4
411
411
  summary: A Ruby REST only client library for ably.io realtime messaging