ably 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -21,7 +21,7 @@ shared_examples 'a realtime model' do |shared_options = {}|
21
21
  let(:model_options) { { action: 5 } }
22
22
 
23
23
  it 'provides access to #json' do
24
- expect(model.json).to eql(model_options)
24
+ expect(model.json).to eq(model_options)
25
25
  end
26
26
  end
27
27
 
@@ -7,20 +7,19 @@ class TestApp
7
7
  ],
8
8
  'namespaces' => [
9
9
  { 'id' => 'persisted', 'persisted' => true }
10
+ ],
11
+ 'channels' => [
12
+ {
13
+ 'name' => 'persisted:presence_fixtures',
14
+ 'presence' => [
15
+ { 'clientId' => 'client_bool', 'clientData' => true },
16
+ { 'clientId' => 'client_int', 'clientData' => 24 },
17
+ { 'clientId' => 'client_string', 'clientData' => 'This is a string clientData payload' },
18
+ { 'clientId' => 'client_json', 'clientData' => { "test" => 'This is a JSONObject clientData payload'} }
19
+ ]
20
+ }
10
21
  ]
11
- # ],
12
- # 'channels' => [
13
- # {
14
- # 'name' => 'persisted:presence_fixtures',
15
- # 'presence' => [
16
- # { 'clientId' => 'client_bool', 'clientData' => true },
17
- # { 'clientId' => 'client_int', 'clientData' => 24 },
18
- # { 'clientId' => 'client_string', 'clientData' => 'This is a string clientData payload' },
19
- # { 'clientId' => 'client_json', 'clientData' => { "test" => 'This is a JSONObject clientData payload'} }
20
- # ]
21
- # }
22
- # ]
23
- }.to_json
22
+ }
24
23
 
25
24
  include Singleton
26
25
 
@@ -32,7 +31,7 @@ class TestApp
32
31
  "Content-Type" => "application/json"
33
32
  }
34
33
 
35
- response = Faraday.post(url, APP_SPEC, headers)
34
+ response = Faraday.post(url, APP_SPEC.to_json, headers)
36
35
 
37
36
  @attributes = JSON.parse(response.body)
38
37
  end
@@ -0,0 +1,72 @@
1
+ require "spec_helper"
2
+
3
+ describe Ably::Modules::Conversions do
4
+ let(:class_with_module) { Class.new do; include Ably::Modules::Conversions; end }
5
+ let(:subject) { class_with_module.new }
6
+ before do
7
+ # make method being tested public
8
+ class_with_module.class_eval %{ public :#{method} }
9
+ end
10
+
11
+ context '#as_since_epoch' do
12
+ let(:method) { :as_since_epoch }
13
+
14
+ context 'with time' do
15
+ let(:time) { Time.new }
16
+
17
+ it 'converts to milliseconds by default' do
18
+ expect(subject.as_since_epoch(time)).to be_within(1).of(time.to_f * 1_000)
19
+ end
20
+
21
+ it 'converted to seconds' do
22
+ expect(subject.as_since_epoch(time, granularity: :s)).to eql(time.to_i)
23
+ end
24
+ end
25
+
26
+ context 'with numeric' do
27
+ it 'converts to integer' do
28
+ expect(subject.as_since_epoch(1.01)).to eql(1)
29
+ end
30
+
31
+ it 'accepts integers' do
32
+ expect(subject.as_since_epoch(1)).to eql(1)
33
+ end
34
+ end
35
+
36
+ context 'with any other object' do
37
+ it 'raises an exception' do
38
+ expect { subject.as_since_epoch(Object.new) }.to raise_error ArgumentError
39
+ end
40
+ end
41
+ end
42
+
43
+ context '#as_time_from_epoch' do
44
+ let(:method) { :as_time_from_epoch }
45
+ let(:time) { Time.new }
46
+
47
+ context 'with numeric' do
48
+ let(:millisecond) { Time.new.to_f * 1_000 }
49
+ let(:seconds) { Time.new.to_f }
50
+
51
+ it 'converts to Time from milliseconds by default' do
52
+ expect(subject.as_time_from_epoch(millisecond).to_f).to be_within(0.001).of(time.to_f)
53
+ end
54
+
55
+ it 'converts to Time from seconds' do
56
+ expect(subject.as_time_from_epoch(seconds, granularity: :s).to_i).to eql(time.to_i)
57
+ end
58
+ end
59
+
60
+ context 'with Time' do
61
+ it 'leaves intact' do
62
+ expect(subject.as_time_from_epoch(time)).to eql(time)
63
+ end
64
+ end
65
+
66
+ context 'with any other object' do
67
+ it 'raises an exception' do
68
+ expect { subject.as_time_from_epoch(Object.new) }.to raise_error ArgumentError
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,295 @@
1
+ require 'spec_helper'
2
+ require 'securerandom'
3
+
4
+ describe Ably::Models::IdiomaticRubyWrapper do
5
+ include Ably::Modules::Conversions
6
+
7
+ let(:mixed_case_data) do
8
+ {
9
+ 'mixedCase' => 'true',
10
+ 'simple' => 'without case',
11
+ 'hashObject' => {
12
+ 'mixedCaseChild' => 'exists'
13
+ },
14
+ 'arrayObject' => [
15
+ {
16
+ 'mixedCaseChild' => 'exists'
17
+ }
18
+ ],
19
+ }
20
+ end
21
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data) }
22
+
23
+ context 'Kernel.Array like method to create a IdiomaticRubyWrapper' do
24
+ it 'will return the same IdiomaticRubyWrapper if passed in' do
25
+ expect(IdiomaticRubyWrapper(subject)).to eql(subject)
26
+ end
27
+
28
+ it 'will return the same IdiomaticRubyWrapper if passed in' do
29
+ expect(IdiomaticRubyWrapper(mixed_case_data)).to be_a(Ably::Models::IdiomaticRubyWrapper)
30
+ end
31
+ end
32
+
33
+ it 'provides accessor method to values using snake_case' do
34
+ expect(subject[:mixed_case]).to eql('true')
35
+ expect(subject[:simple]).to eql('without case')
36
+ end
37
+
38
+ it 'provides methods to read values using snake_case' do
39
+ expect(subject.mixed_case).to eql('true')
40
+ expect(subject.simple).to eql('without case')
41
+ end
42
+
43
+ it 'provides accessor set method to values using snake_case' do
44
+ subject[:mixed_case] = 'mixedCase'
45
+ subject[:simple] = 'simple'
46
+ expect(subject[:mixed_case]).to eql('mixedCase')
47
+ expect(subject[:simple]).to eql('simple')
48
+ end
49
+
50
+ it 'provides methods to write values using snake_case' do
51
+ subject.mixed_case = 'mixedCase'
52
+ subject.simple = 'simple'
53
+ expect(subject.mixed_case).to eql('mixedCase')
54
+ expect(subject.simple).to eql('simple')
55
+ end
56
+
57
+ it 'does not provide methods for keys that are missing' do
58
+ expect { subject.no_key_exists_for_this }.to raise_error NoMethodError
59
+ end
60
+
61
+ specify '#json returns raw JSON object' do
62
+ expect(subject.json).to eql(mixed_case_data)
63
+ end
64
+
65
+ context 'recursively wrapping child objects' do
66
+ it 'wraps Hashes' do
67
+ expect(subject.hash_object.mixed_case_child).to eql('exists')
68
+ end
69
+
70
+ it 'ignores arrays' do
71
+ expect(subject.array_object.first).to include('mixedCaseChild' => 'exists')
72
+ end
73
+
74
+ context ':stop_at option' do
75
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data, stop_at: stop_at) }
76
+
77
+ context 'with symbol' do
78
+ let(:stop_at) { :hash_object }
79
+
80
+ it 'does not wrap the matching key' do
81
+ expect(subject.hash_object).to include('mixedCaseChild' => 'exists')
82
+ end
83
+ end
84
+
85
+ context 'with string' do
86
+ let(:stop_at) { ['hashObject'] }
87
+
88
+ it 'does not wrap the matching key' do
89
+ expect(subject.hash_object).to include('mixedCaseChild' => 'exists')
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ context 'non standard mixedCaseData' do
96
+ let(:data) do
97
+ {
98
+ :symbol => 'aSymbolValue',
99
+ :snake_case_symbol => 'snake_case_symbolValue',
100
+ :mixedCaseSymbol => 'mixedCaseSymbolValue',
101
+ 'snake_case_string' => 'snake_case_stringValue',
102
+ 'mixedCaseString' => 'mixedCaseStringFirstChoiceValue',
103
+ :mixedCaseString => 'mixedCaseStringFallbackValue',
104
+ :CamelCaseSymbol => 'CamelCaseSymbolValue',
105
+ 'CamelCaseString' => 'camel_case_stringValue',
106
+ :lowercasesymbol => 'lowercasesymbolValue',
107
+ 'lowercasestring' => 'lowercasestringValue'
108
+ }
109
+ end
110
+ let(:unique_value) { SecureRandom.hex }
111
+
112
+ subject { Ably::Models::IdiomaticRubyWrapper.new(data) }
113
+
114
+ {
115
+ :symbol => 'aSymbolValue',
116
+ :snake_case_symbol => 'snake_case_symbolValue',
117
+ :mixed_case_symbol => 'mixedCaseSymbolValue',
118
+ :snake_case_string => 'snake_case_stringValue',
119
+ :mixed_case_string => 'mixedCaseStringFirstChoiceValue',
120
+ :camel_case_symbol => 'CamelCaseSymbolValue',
121
+ :camel_case_string => 'camel_case_stringValue',
122
+ :lower_case_symbol => 'lowercasesymbolValue',
123
+ :lower_case_string => 'lowercasestringValue'
124
+ }.each do |symbol_accessor, expected_value|
125
+ context symbol_accessor do
126
+ it 'allows access to non conformant keys but prefers correct mixedCaseSyntax' do
127
+ expect(subject[symbol_accessor]).to eql(expected_value)
128
+ end
129
+
130
+ context 'updates' do
131
+ before do
132
+ subject[symbol_accessor] = unique_value
133
+ end
134
+
135
+ it 'returns the new value' do
136
+ expect(subject[symbol_accessor]).to eql(unique_value)
137
+ end
138
+
139
+ it 'returns the new value in the JSON' do
140
+ expect(subject.to_json).to include(unique_value)
141
+ expect(subject.to_json).to_not include(expected_value)
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ it 'returns nil for non existent keys' do
148
+ expect(subject[:non_existent_key]).to eql(nil)
149
+ end
150
+
151
+ context 'new keys' do
152
+ before do
153
+ subject[:new_key] = 'new_value'
154
+ end
155
+
156
+ it 'uses mixedCase' do
157
+ expect(subject.json['newKey']).to eql('new_value')
158
+ expect(subject.new_key).to eql('new_value')
159
+ end
160
+ end
161
+ end
162
+
163
+ context 'acts like a duck' do
164
+ specify '#to_json returns JSON stringified' do
165
+ expect(subject.to_json).to eql(mixed_case_data.to_json)
166
+ end
167
+
168
+ context '#to_json with changes' do
169
+ before do
170
+ @original_mixed_case_data = mixed_case_data.to_json
171
+ subject[:mixed_case] = 'new_value'
172
+ end
173
+
174
+ it 'returns stringified JSON with changes' do
175
+ expect(subject.to_json).to_not eql(@original_mixed_case_data)
176
+ expect(subject.to_json).to match('new_value')
177
+ end
178
+ end
179
+
180
+ it 'returns correct size' do
181
+ expect(subject.size).to eql(mixed_case_data.size)
182
+ end
183
+
184
+ it 'supports Hash-like #keys' do
185
+ expect(subject.keys.length).to eql(mixed_case_data.keys.length)
186
+ end
187
+
188
+ it 'supports Hash-like #values' do
189
+ expect(subject.values.length).to eql(mixed_case_data.values.length)
190
+ end
191
+
192
+ it 'is Enumerable' do
193
+ expect(subject).to be_kind_of(Enumerable)
194
+ end
195
+
196
+ context 'iterable' do
197
+ subject { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data, stop_at: [:hash_object, :array_object]) }
198
+
199
+ it 'yields key value pairs' do
200
+ expect(subject.map { |k,v| k }).to eql([:mixed_case, :simple, :hash_object, :array_object])
201
+ expect(subject.map { |k,v| v }).to eql(mixed_case_data.map { |k,v| v })
202
+ end
203
+ end
204
+
205
+ context '#fetch' do
206
+ it 'fetches the key' do
207
+ expect(subject.fetch(:mixed_case)).to eql('true')
208
+ end
209
+
210
+ it 'raise an exception if key does not exist' do
211
+ expect { subject.fetch(:non_existent) }.to raise_error KeyError, /key not found: non_existent/
212
+ end
213
+
214
+ it 'allows a default value argument' do
215
+ expect(subject.fetch(:non_existent, 'default')).to eql('default')
216
+ end
217
+
218
+ it 'calls the block if key does not exist' do
219
+ expect(subject.fetch(:non_existent) { 'block_default' } ).to eql('block_default')
220
+ end
221
+ end
222
+
223
+ context '#==' do
224
+ let(:mixed_case_data) do
225
+ {
226
+ 'key' => 'value'
227
+ }
228
+ end
229
+ let(:presented_as_data) do
230
+ {
231
+ :key => 'value'
232
+ }
233
+ end
234
+ let(:invalid_match) do
235
+ {
236
+ :key => 'other value'
237
+ }
238
+ end
239
+ let(:other) { Ably::Models::IdiomaticRubyWrapper.new(mixed_case_data) }
240
+ let(:other_invalid) { Ably::Models::IdiomaticRubyWrapper.new(invalid_match) }
241
+
242
+ it 'presents itself as a symbolized version of the object' do
243
+ expect(subject).to eq(presented_as_data)
244
+ end
245
+
246
+ it 'returns false if different values to another Hash' do
247
+ expect(subject).to_not eq(invalid_match)
248
+ end
249
+
250
+ it 'compares with itself' do
251
+ expect(subject).to eq(other)
252
+ end
253
+
254
+ it 'returns false if different values to another IdiomaticRubyWrapper' do
255
+ expect(subject).to_not eq(other_invalid)
256
+ end
257
+
258
+ it 'returns false if comparing with a non Hash/IdiomaticRubyWrapper object' do
259
+ expect(subject).to_not eq(Object)
260
+ end
261
+ end
262
+
263
+ context '#to_hash' do
264
+ let(:mixed_case_data) do
265
+ {
266
+ 'key' => 'value'
267
+ }
268
+ end
269
+
270
+ it 'returns a hash' do
271
+ expect(subject.to_hash).to include(key: 'value')
272
+ end
273
+ end
274
+
275
+ context '#dup' do
276
+ let(:mixed_case_data) do
277
+ {
278
+ 'key' => 'value'
279
+ }.freeze
280
+ end
281
+ let(:dupe) { subject.dup }
282
+
283
+ it 'returns a new object with the underlying JSON duped' do
284
+ expect(subject.json).to be_frozen
285
+ expect(dupe.json).to_not be_frozen
286
+ end
287
+
288
+ it 'returns a new IdiomaticRubyWrapper with the same underlying Hash object' do
289
+ expect(dupe).to be_a(Ably::Models::IdiomaticRubyWrapper)
290
+ expect(dupe.json).to be_a(Hash)
291
+ expect(dupe.json).to eql(mixed_case_data)
292
+ end
293
+ end
294
+ end
295
+ end
@@ -2,6 +2,8 @@ require 'spec_helper'
2
2
  require 'support/model_helper'
3
3
 
4
4
  describe Ably::Realtime::Models::Message do
5
+ include Ably::Modules::Conversions
6
+
5
7
  subject { Ably::Realtime::Models::Message }
6
8
  let(:protocol_message) { Ably::Realtime::Models::ProtocolMessage.new(action: 1) }
7
9
 
@@ -10,7 +12,7 @@ describe Ably::Realtime::Models::Message do
10
12
  end
11
13
 
12
14
  context '#sender_timestamp' do
13
- let(:model) { subject.new({ timestamp: Time.now.to_i * 1000 }, protocol_message) }
15
+ let(:model) { subject.new({ timestamp: as_since_epoch(Time.now) }, protocol_message) }
14
16
  it 'retrieves attribute :sender_timestamp' do
15
17
  expect(model.sender_timestamp).to be_a(Time)
16
18
  expect(model.sender_timestamp.to_i).to be_within(1).of(Time.now.to_i)
@@ -36,7 +38,7 @@ describe Ably::Realtime::Models::Message do
36
38
  end
37
39
 
38
40
  it 'autofills a missing timestamp for all messages' do
39
- expect(json_object["timestamp"].to_i / 1000).to be_within(1).of(Time.now.to_i)
41
+ expect(json_object["timestamp"].to_i).to be_within(1).of(as_since_epoch(Time.now))
40
42
  end
41
43
  end
42
44
 
@@ -2,6 +2,7 @@ require 'spec_helper'
2
2
  require 'support/model_helper'
3
3
 
4
4
  describe Ably::Realtime::Models::ProtocolMessage do
5
+ include Ably::Modules::Conversions
5
6
  subject { Ably::Realtime::Models::ProtocolMessage }
6
7
 
7
8
  it_behaves_like 'a realtime model',
@@ -30,7 +31,7 @@ describe Ably::Realtime::Models::ProtocolMessage do
30
31
  end
31
32
 
32
33
  context '#timestamp' do
33
- let(:protocol_message) { subject.new(timestamp: Time.now.to_i * 1000) }
34
+ let(:protocol_message) { subject.new(timestamp: as_since_epoch(Time.now)) }
34
35
  it 'retrieves attribute :timestamp' do
35
36
  expect(protocol_message.timestamp).to be_a(Time)
36
37
  expect(protocol_message.timestamp.to_i).to be_within(1).of(Time.now.to_i)