ably 0.1.2 → 0.1.3

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