pusher 0.5.1 → 0.5.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.
data/CHANGELOG CHANGED
@@ -4,3 +4,8 @@
4
4
 
5
5
  * Use ActiveSupport::JSON.encode instead of #to_json to avoid conflicts with JSON gem. [OL]
6
6
  * Support loading Pusher credentials from a PUSHER_URL environment variable. [ML]
7
+
8
+ * Pusher v0.5.2 (25th May, 2010) *
9
+
10
+ * Bugfix: required uri correctly
11
+ * Bugfix: channel objects not being reused
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.1
1
+ 0.5.2
data/lib/pusher.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  autoload 'Logger', 'logger'
2
+ require 'uri'
2
3
 
3
4
  module Pusher
4
5
  class Error < RuntimeError; end
5
6
  class AuthenticationError < Error; end
7
+ class ConfigurationError < Error; end
6
8
 
7
9
  class << self
8
10
  attr_accessor :host, :port
@@ -55,9 +57,9 @@ module Pusher
55
57
  end
56
58
 
57
59
  def self.[](channel_name)
58
- raise ArgumentError, 'Missing configuration: please check that Pusher.url is configured' unless configured?
60
+ raise ConfigurationError, 'Missing configuration: please check that Pusher.url is configured' unless configured?
59
61
  @channels ||= {}
60
- @channels[channel_name.to_s] = Channel.new(url, channel_name)
62
+ @channels[channel_name.to_s] ||= Channel.new(url, channel_name)
61
63
  end
62
64
  end
63
65
 
@@ -1,11 +1,6 @@
1
1
  require 'crack/core_extensions' # Used for Hash#to_params
2
- require 'signature'
3
- require 'digest/md5'
4
2
  require 'hmac-sha2'
5
3
 
6
- require 'json'
7
- require 'uri'
8
-
9
4
  module Pusher
10
5
  class Channel
11
6
  attr_reader :name
@@ -1,3 +1,7 @@
1
+ require 'signature'
2
+ require 'digest/md5'
3
+ require 'json'
4
+
1
5
  module Pusher
2
6
  class Request
3
7
  attr_reader :body, :query
data/pusher.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{pusher}
8
- s.version = "0.5.1"
8
+ s.version = "0.5.2"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["New Bamboo"]
12
- s.date = %q{2010-05-20}
12
+ s.date = %q{2010-05-25}
13
13
  s.description = %q{Wrapper for pusherapp.com REST api}
14
14
  s.email = %q{support@pusherapp.com}
15
15
  s.extra_rdoc_files = [
@@ -28,6 +28,7 @@ Gem::Specification.new do |s|
28
28
  "lib/pusher/channel.rb",
29
29
  "lib/pusher/request.rb",
30
30
  "pusher.gemspec",
31
+ "spec/channel_spec.rb",
31
32
  "spec/pusher_spec.rb",
32
33
  "spec/spec.opts",
33
34
  "spec/spec_helper.rb"
@@ -38,7 +39,8 @@ Gem::Specification.new do |s|
38
39
  s.rubygems_version = %q{1.3.6}
39
40
  s.summary = %q{Pusher App client}
40
41
  s.test_files = [
41
- "spec/pusher_spec.rb",
42
+ "spec/channel_spec.rb",
43
+ "spec/pusher_spec.rb",
42
44
  "spec/spec_helper.rb"
43
45
  ]
44
46
 
@@ -0,0 +1,239 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Pusher::Channel do
4
+ before do
5
+ Pusher.app_id = '20'
6
+ Pusher.key = '12345678900000001'
7
+ Pusher.secret = '12345678900000001'
8
+ end
9
+
10
+ after do
11
+ Pusher.app_id = nil
12
+ Pusher.key = nil
13
+ Pusher.secret = nil
14
+ end
15
+
16
+ describe 'trigger!' do
17
+ before :each do
18
+ WebMock.stub_request(
19
+ :post, %r{/apps/20/channels/test_channel/events}
20
+ ).to_return(:status => 202)
21
+ @channel = Pusher['test_channel']
22
+ end
23
+
24
+ it 'should configure HTTP library to talk to pusher API' do
25
+ @channel.trigger!('new_event', 'Some data')
26
+ WebMock.request(:post, %r{api.pusherapp.com}).should have_been_made
27
+ end
28
+
29
+ it 'should POST with the correct parameters and convert data to JSON' do
30
+ @channel.trigger!('new_event', {
31
+ :name => 'Pusher',
32
+ :last_name => 'App'
33
+ })
34
+ WebMock.request(:post, %r{/apps/20/channels/test_channel/events}).
35
+ with do |req|
36
+
37
+ query_hash = req.uri.query_values
38
+ query_hash["name"].should == 'new_event'
39
+ query_hash["auth_key"].should == Pusher.key
40
+ query_hash["auth_timestamp"].should_not be_nil
41
+
42
+ parsed = JSON.parse(req.body)
43
+ parsed.should == {
44
+ "name" => 'Pusher',
45
+ "last_name" => 'App'
46
+ }
47
+
48
+ req.headers['Content-Type'].should == 'application/json'
49
+ end.should have_been_made
50
+ end
51
+
52
+ it "should handle string data by sending unmodified in body" do
53
+ string = "foo\nbar\""
54
+ @channel.trigger!('new_event', string)
55
+ WebMock.request(:post, %r{/apps/20/channels/test_channel/events}).with do |req|
56
+ req.body.should == "foo\nbar\""
57
+ end.should have_been_made
58
+ end
59
+
60
+ it "should raise error if an object sent which canot be JSONified" do
61
+ lambda {
62
+ @channel.trigger!('new_event', Object.new)
63
+ }.should raise_error(JSON::GeneratorError)
64
+ end
65
+
66
+ it "should propagate exception if exception raised" do
67
+ WebMock.stub_request(
68
+ :post, %r{/apps/20/channels/test_channel/events}
69
+ ).to_raise(RuntimeError)
70
+ lambda {
71
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
72
+ }.should raise_error(RuntimeError)
73
+ end
74
+
75
+ it "should raise AuthenticationError if pusher returns 401" do
76
+ WebMock.stub_request(
77
+ :post,
78
+ %r{/apps/20/channels/test_channel/events}
79
+ ).to_return(:status => 401)
80
+ lambda {
81
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
82
+ }.should raise_error(Pusher::AuthenticationError)
83
+ end
84
+
85
+ it "should raise Pusher::Error if pusher returns 404" do
86
+ WebMock.stub_request(
87
+ :post, %r{/apps/20/channels/test_channel/events}
88
+ ).to_return(:status => 404)
89
+ lambda {
90
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
91
+ }.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
92
+ end
93
+
94
+ it "should raise Pusher::Error if pusher returns 500" do
95
+ WebMock.stub_request(
96
+ :post, %r{/apps/20/channels/test_channel/events}
97
+ ).to_return(:status => 500, :body => "some error")
98
+ lambda {
99
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
100
+ }.should raise_error(Pusher::Error, 'Unknown error in Pusher: some error')
101
+ end
102
+ end
103
+
104
+ describe 'trigger' do
105
+ before :each do
106
+ @http = mock('HTTP', :post => 'posting')
107
+ Net::HTTP.stub!(:new).and_return @http
108
+ end
109
+
110
+ it "should log failure if exception raised" do
111
+ @http.should_receive(:post).and_raise("Fail")
112
+ Pusher.logger.should_receive(:error).with("Fail (RuntimeError)")
113
+ Pusher.logger.should_receive(:debug) #backtrace
114
+ Pusher::Channel.new(Pusher.url, 'test_channel').trigger('new_event', 'Some data')
115
+ end
116
+
117
+ it "should log failure if exception raised" do
118
+ @http.should_receive(:post).and_raise("Fail")
119
+ Pusher.logger.should_receive(:error).with("Fail (RuntimeError)")
120
+ Pusher.logger.should_receive(:debug) #backtrace
121
+ Pusher::Channel.new(Pusher.url, 'test_channel').trigger('new_event', 'Some data')
122
+ end
123
+ end
124
+
125
+ describe "trigger_async" do
126
+ before :each do
127
+ EM.send(:remove_const, :HttpRequest)
128
+ EM::HttpRequest = EM::MockHttpRequest
129
+
130
+ #in order to match URLs when testing http requests
131
+ #override the method that converts query hash to string
132
+ #to include a sort so URL is consistent
133
+ module EventMachine
134
+ module HttpEncoding
135
+ def encode_query(path, query, uri_query)
136
+ encoded_query = if query.kind_of?(Hash)
137
+ query.sort{|a, b| a.to_s <=> b.to_s}.
138
+ map { |k, v| encode_param(k, v) }.
139
+ join('&')
140
+ else
141
+ query.to_s
142
+ end
143
+ if !uri_query.to_s.empty?
144
+ encoded_query = [encoded_query, uri_query].reject {|part| part.empty?}.join("&")
145
+ end
146
+ return path if encoded_query.to_s.empty?
147
+ "#{path}?#{encoded_query}"
148
+ end
149
+ end
150
+ end
151
+
152
+ EM::HttpRequest.reset_registry!
153
+ EM::HttpRequest.reset_counts!
154
+ EM::HttpRequest.pass_through_requests = false
155
+ end
156
+
157
+ it "should return a deferrable which succeeds in success case" do
158
+ # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
159
+ Time.stub(:now).and_return(123)
160
+
161
+ url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
162
+
163
+ data = <<-RESPONSE.gsub(/^ +/, '')
164
+ HTTP/1.1 202 Accepted
165
+ Content-Type: text/html
166
+ Content-Length: 13
167
+ Connection: keep-alive
168
+ Server: thin 1.2.7 codename No Hup
169
+
170
+ 202 ACCEPTED
171
+ RESPONSE
172
+
173
+ EM::HttpRequest.register(url, :post, data)
174
+
175
+ EM.run {
176
+ d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
177
+ d.callback {
178
+ EM::HttpRequest.count(url, :post).should == 1
179
+ EM.stop
180
+ }
181
+ d.errback {
182
+ fail
183
+ EM.stop
184
+ }
185
+ }
186
+ end
187
+
188
+ it "should return a deferrable which fails (with exception) in fail case" do
189
+ # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
190
+ Time.stub(:now).and_return(123)
191
+
192
+ url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
193
+
194
+ data = <<-RESPONSE.gsub(/^ +/, '')
195
+ HTTP/1.1 401 Unauthorized
196
+ Content-Type: text/html
197
+ Content-Length: 130
198
+ Connection: keep-alive
199
+ Server: thin 1.2.7 codename No Hup
200
+
201
+ 401 UNAUTHORIZED: Timestamp expired: Given timestamp (2010-05-05T11:24:42Z) not within 600s of server time (2010-05-05T11:51:42Z)
202
+ RESPONSE
203
+
204
+ EM::HttpRequest.register(url, :post, data)
205
+
206
+ EM.run {
207
+ d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
208
+ d.callback {
209
+ fail
210
+ }
211
+ d.errback { |error|
212
+ EM::HttpRequest.count(url, :post).should == 1
213
+ error.should be_kind_of(Pusher::AuthenticationError)
214
+ EM.stop
215
+ }
216
+ }
217
+ end
218
+ end
219
+
220
+ describe "socket_auth" do
221
+ before :each do
222
+ @channel = Pusher['test_channel']
223
+ end
224
+
225
+ it "should return an authentication string given a socket id" do
226
+ auth = @channel.socket_auth('socketid')
227
+
228
+ auth.should == '12345678900000001:827076f551e22451357939e4c7bb1200de29f921d5bf80b40d71668f9cd61c40'
229
+ end
230
+
231
+ it "should raise error if authentication is invalid" do
232
+ [nil, ''].each do |invalid|
233
+ lambda {
234
+ @channel.socket_auth(invalid)
235
+ }.should raise_error
236
+ end
237
+ end
238
+ end
239
+ end
data/spec/pusher_spec.rb CHANGED
@@ -48,7 +48,7 @@ describe Pusher do
48
48
  end
49
49
  end
50
50
 
51
- describe 'configured' do
51
+ describe 'when configured' do
52
52
  before do
53
53
  Pusher.app_id = '20'
54
54
  Pusher.key = '12345678900000001'
@@ -66,238 +66,22 @@ describe Pusher do
66
66
  @channel = Pusher['test_channel']
67
67
  end
68
68
 
69
- it 'should return a new channel' do
69
+ it 'should return a channel' do
70
70
  @channel.should be_kind_of(Pusher::Channel)
71
71
  end
72
72
 
73
+ it "should reuse the same channel objects" do
74
+ channel1, channel2 = Pusher['test_channel'], Pusher['test_channel']
75
+
76
+ channel1.object_id.should == channel2.object_id
77
+ end
78
+
73
79
  %w{app_id key secret}.each do |config|
74
80
  it "should raise exception if #{config} not configured" do
75
81
  Pusher.send("#{config}=", nil)
76
82
  lambda {
77
83
  Pusher['test_channel']
78
- }.should raise_error(ArgumentError)
79
- end
80
- end
81
- end
82
-
83
- describe 'Channel#trigger!' do
84
- before :each do
85
- WebMock.stub_request(
86
- :post, %r{/apps/20/channels/test_channel/events}
87
- ).to_return(:status => 202)
88
- @channel = Pusher['test_channel']
89
- end
90
-
91
- it 'should configure HTTP library to talk to pusher API' do
92
- @channel.trigger!('new_event', 'Some data')
93
- WebMock.request(:post, %r{api.pusherapp.com}).should have_been_made
94
- end
95
-
96
- it 'should POST with the correct parameters and convert data to JSON' do
97
- @channel.trigger!('new_event', {
98
- :name => 'Pusher',
99
- :last_name => 'App'
100
- })
101
- WebMock.request(:post, %r{/apps/20/channels/test_channel/events}).
102
- with do |req|
103
-
104
- query_hash = req.uri.query_values
105
- query_hash["name"].should == 'new_event'
106
- query_hash["auth_key"].should == Pusher.key
107
- query_hash["auth_timestamp"].should_not be_nil
108
-
109
- parsed = JSON.parse(req.body)
110
- parsed.should == {
111
- "name" => 'Pusher',
112
- "last_name" => 'App'
113
- }
114
-
115
- req.headers['Content-Type'].should == 'application/json'
116
- end.should have_been_made
117
- end
118
-
119
- it "should handle string data by sending unmodified in body" do
120
- string = "foo\nbar\""
121
- @channel.trigger!('new_event', string)
122
- WebMock.request(:post, %r{/apps/20/channels/test_channel/events}).with do |req|
123
- req.body.should == "foo\nbar\""
124
- end.should have_been_made
125
- end
126
-
127
- it "should raise error if an object sent which canot be JSONified" do
128
- lambda {
129
- @channel.trigger!('new_event', Object.new)
130
- }.should raise_error(JSON::GeneratorError)
131
- end
132
-
133
- it "should propagate exception if exception raised" do
134
- WebMock.stub_request(
135
- :post, %r{/apps/20/channels/test_channel/events}
136
- ).to_raise(RuntimeError)
137
- lambda {
138
- Pusher['test_channel'].trigger!('new_event', 'Some data')
139
- }.should raise_error(RuntimeError)
140
- end
141
-
142
- it "should raise AuthenticationError if pusher returns 401" do
143
- WebMock.stub_request(
144
- :post,
145
- %r{/apps/20/channels/test_channel/events}
146
- ).to_return(:status => 401)
147
- lambda {
148
- Pusher['test_channel'].trigger!('new_event', 'Some data')
149
- }.should raise_error(Pusher::AuthenticationError)
150
- end
151
-
152
- it "should raise Pusher::Error if pusher returns 404" do
153
- WebMock.stub_request(
154
- :post, %r{/apps/20/channels/test_channel/events}
155
- ).to_return(:status => 404)
156
- lambda {
157
- Pusher['test_channel'].trigger!('new_event', 'Some data')
158
- }.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
159
- end
160
-
161
- it "should raise Pusher::Error if pusher returns 500" do
162
- WebMock.stub_request(
163
- :post, %r{/apps/20/channels/test_channel/events}
164
- ).to_return(:status => 500, :body => "some error")
165
- lambda {
166
- Pusher['test_channel'].trigger!('new_event', 'Some data')
167
- }.should raise_error(Pusher::Error, 'Unknown error in Pusher: some error')
168
- end
169
- end
170
-
171
- describe 'Channel#trigger' do
172
- before :each do
173
- @http = mock('HTTP', :post => 'posting')
174
- Net::HTTP.stub!(:new).and_return @http
175
- end
176
-
177
- it "should log failure if exception raised" do
178
- @http.should_receive(:post).and_raise("Fail")
179
- Pusher.logger.should_receive(:error).with("Fail (RuntimeError)")
180
- Pusher.logger.should_receive(:debug) #backtrace
181
- Pusher['test_channel'].trigger('new_event', 'Some data')
182
- end
183
-
184
- it "should log failure if exception raised" do
185
- @http.should_receive(:post).and_raise("Fail")
186
- Pusher.logger.should_receive(:error).with("Fail (RuntimeError)")
187
- Pusher.logger.should_receive(:debug) #backtrace
188
- Pusher['test_channel'].trigger('new_event', 'Some data')
189
- end
190
- end
191
-
192
- describe "Channel#trigger_async" do
193
- #in order to match URLs when testing http requests
194
- #override the method that converts query hash to string
195
- #to include a sort so URL is consistent
196
- module EventMachine
197
- module HttpEncoding
198
- def encode_query(path, query, uri_query)
199
- encoded_query = if query.kind_of?(Hash)
200
- query.sort{|a, b| a.to_s <=> b.to_s}.
201
- map { |k, v| encode_param(k, v) }.
202
- join('&')
203
- else
204
- query.to_s
205
- end
206
- if !uri_query.to_s.empty?
207
- encoded_query = [encoded_query, uri_query].reject {|part| part.empty?}.join("&")
208
- end
209
- return path if encoded_query.to_s.empty?
210
- "#{path}?#{encoded_query}"
211
- end
212
- end
213
- end
214
-
215
- before :each do
216
- EM::HttpRequest = EM::MockHttpRequest
217
- EM::HttpRequest.reset_registry!
218
- EM::HttpRequest.reset_counts!
219
- EM::HttpRequest.pass_through_requests = false
220
- end
221
-
222
- it "should return a deferrable which succeeds in success case" do
223
- # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
224
- Time.stub(:now).and_return(123)
225
-
226
- url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
227
-
228
- data = <<-RESPONSE.gsub(/^ +/, '')
229
- HTTP/1.1 202 Accepted
230
- Content-Type: text/html
231
- Content-Length: 13
232
- Connection: keep-alive
233
- Server: thin 1.2.7 codename No Hup
234
-
235
- 202 ACCEPTED
236
- RESPONSE
237
-
238
- EM::HttpRequest.register(url, :post, data)
239
-
240
- EM.run {
241
- d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
242
- d.callback {
243
- EM::HttpRequest.count(url, :post).should == 1
244
- EM.stop
245
- }
246
- d.errback {
247
- fail
248
- EM.stop
249
- }
250
- }
251
- end
252
-
253
- it "should return a deferrable which fails (with exception) in fail case" do
254
- # Yeah, mocking EM::MockHttpRequest isn't that feature rich :)
255
- Time.stub(:now).and_return(123)
256
-
257
- url = 'http://api.pusherapp.com:80/apps/20/channels/test_channel/events?auth_key=12345678900000001&auth_signature=0ffe2a3749f886ca69c3f516a30c7bc9a12d2ebd8bda5b718b90ad58507c8261&auth_timestamp=123&auth_version=1.0&body_md5=5b82f8bf4df2bfb0e66ccaa7306fd024&name=new_event'
258
-
259
- data = <<-RESPONSE.gsub(/^ +/, '')
260
- HTTP/1.1 401 Unauthorized
261
- Content-Type: text/html
262
- Content-Length: 130
263
- Connection: keep-alive
264
- Server: thin 1.2.7 codename No Hup
265
-
266
- 401 UNAUTHORIZED: Timestamp expired: Given timestamp (2010-05-05T11:24:42Z) not within 600s of server time (2010-05-05T11:51:42Z)
267
- RESPONSE
268
-
269
- EM::HttpRequest.register(url, :post, data)
270
-
271
- EM.run {
272
- d = Pusher['test_channel'].trigger_async('new_event', 'Some data')
273
- d.callback {
274
- fail
275
- }
276
- d.errback { |error|
277
- EM::HttpRequest.count(url, :post).should == 1
278
- error.should be_kind_of(Pusher::AuthenticationError)
279
- EM.stop
280
- }
281
- }
282
- end
283
- end
284
-
285
- describe "Channel#socket_auth" do
286
- before :each do
287
- @channel = Pusher['test_channel']
288
- end
289
-
290
- it "should return an authentication string given a socket id" do
291
- auth = @channel.socket_auth('socketid')
292
-
293
- auth.should == '12345678900000001:827076f551e22451357939e4c7bb1200de29f921d5bf80b40d71668f9cd61c40'
294
- end
295
-
296
- it "should raise error if authentication is invalid" do
297
- [nil, ''].each do |invalid|
298
- lambda {
299
- @channel.socket_auth(invalid)
300
- }.should raise_error
84
+ }.should raise_error(Pusher::ConfigurationError)
301
85
  end
302
86
  end
303
87
  end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 5
8
- - 1
9
- version: 0.5.1
8
+ - 2
9
+ version: 0.5.2
10
10
  platform: ruby
11
11
  authors:
12
12
  - New Bamboo
@@ -14,7 +14,7 @@ autorequire:
14
14
  bindir: bin
15
15
  cert_chain: []
16
16
 
17
- date: 2010-05-20 00:00:00 +01:00
17
+ date: 2010-05-25 00:00:00 +01:00
18
18
  default_executable:
19
19
  dependencies:
20
20
  - !ruby/object:Gem::Dependency
@@ -112,6 +112,7 @@ files:
112
112
  - lib/pusher/channel.rb
113
113
  - lib/pusher/request.rb
114
114
  - pusher.gemspec
115
+ - spec/channel_spec.rb
115
116
  - spec/pusher_spec.rb
116
117
  - spec/spec.opts
117
118
  - spec/spec_helper.rb
@@ -146,5 +147,6 @@ signing_key:
146
147
  specification_version: 3
147
148
  summary: Pusher App client
148
149
  test_files:
150
+ - spec/channel_spec.rb
149
151
  - spec/pusher_spec.rb
150
152
  - spec/spec_helper.rb