pusher 0.3.5 → 0.4.0.beta

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/README.md CHANGED
@@ -6,13 +6,18 @@ Getting started
6
6
 
7
7
  After registering at <http://pusherapp.com> configure your app with the security credentials
8
8
 
9
+ Pusher.app_id = 'your-pusher-app-id'
9
10
  Pusher.key = 'your-pusher-key'
10
11
  Pusher.secret = 'your-pusher-secret'
11
12
 
12
- Trigger an event
13
+ Trigger an event. Channel and event names may only contain alphanumeric characters, '-' and '_'.
14
+
15
+ Pusher['a_channel'].trigger('an_event', {:some => 'data'})
16
+
17
+ Optionally a socket id may be provided. This will prevent the event from being triggered on this specific socket id (see <http://pusherapp.com/docs/duplicates> for more info).
18
+
19
+ Pusher['a_channel'].trigger('an_event', {:some => 'data'}, socket_id)
13
20
 
14
- Pusher['arbitrary-channel-name'].trigger({:some => 'data'})
15
-
16
21
  Logging
17
22
  -------
18
23
 
data/Rakefile CHANGED
@@ -12,7 +12,9 @@ begin
12
12
  gem.authors = ["New Bamboo"]
13
13
  # gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
14
14
  gem.add_dependency "json"
15
+ gem.add_dependency "crack"
15
16
  gem.add_development_dependency "rspec", ">= 1.2.9"
17
+ gem.add_development_dependency "webmock"
16
18
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
19
  end
18
20
  Jeweler::GemcutterTasks.new
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.3.5
1
+ 0.4.0.beta
data/lib/pusher.rb CHANGED
@@ -5,16 +5,13 @@ require 'net/http'
5
5
  autoload 'Logger', 'logger'
6
6
 
7
7
  module Pusher
8
- class ArgumentError < ::ArgumentError
9
- def message
10
- 'You must configure both Pusher.key in order to authenticate your Pusher app'
11
- end
12
- end
8
+ class Error < RuntimeError; end
9
+ class AuthenticationError < Error; end
13
10
 
14
11
  class << self
15
12
  attr_accessor :host, :port
16
13
  attr_writer :logger
17
- attr_accessor :key, :secret
14
+ attr_accessor :app_id, :key, :secret
18
15
 
19
16
  def logger
20
17
  @logger ||= begin
@@ -23,16 +20,21 @@ module Pusher
23
20
  log
24
21
  end
25
22
  end
23
+
24
+ def authentication_token
25
+ Authentication::Token.new(@key, @secret)
26
+ end
26
27
  end
27
28
 
28
29
  self.host = 'api.pusherapp.com'
29
30
  self.port = 80
30
31
 
31
- def self.[](channel_id)
32
- raise ArgumentError unless @key
32
+ def self.[](channel_name)
33
+ raise ArgumentError, 'Missing configuration: please check that Pusher.app_id, Pusher.key, and Pusher.secret are all configured' unless @app_id && @key && @secret
33
34
  @channels ||= {}
34
- @channels[channel_id.to_s] = Channel.new(@key, channel_id)
35
+ @channels[channel_name.to_s] = Channel.new(@app_id, channel_name)
35
36
  end
36
37
  end
37
38
 
38
39
  require 'pusher/channel'
40
+ require 'pusher/authentication'
@@ -0,0 +1,144 @@
1
+ require 'hmac-sha2'
2
+ require 'base64'
3
+
4
+ module Authentication
5
+ class AuthenticationError < RuntimeError; end
6
+
7
+ class Token
8
+ attr_reader :key, :secret
9
+
10
+ def initialize(key, secret)
11
+ @key, @secret = key, secret
12
+ end
13
+
14
+ def sign(request)
15
+ request.sign(self)
16
+ end
17
+ end
18
+
19
+ class Request
20
+ attr_accessor :path, :query_hash
21
+
22
+ # http://www.w3.org/TR/NOTE-datetime
23
+ ISO8601 = "%Y-%m-%dT%H:%M:%SZ"
24
+
25
+ def initialize(method, path, query)
26
+ raise ArgumentError, "Expected string" unless path.kind_of?(String)
27
+ raise ArgumentError, "Expected hash" unless query.kind_of?(Hash)
28
+
29
+ query_hash = {}
30
+ auth_hash = {}
31
+ query.each do |key, v|
32
+ k = key.to_s.downcase
33
+ k[0..4] == 'auth_' ? auth_hash[k] = v : query_hash[k] = v
34
+ end
35
+
36
+ @method = method.upcase
37
+ @path, @query_hash, @auth_hash = path, query_hash, auth_hash
38
+ end
39
+
40
+ def sign(token)
41
+ @auth_hash = {
42
+ :auth_version => "1.0",
43
+ :auth_key => token.key,
44
+ :auth_timestamp => Time.now.to_i
45
+ }
46
+
47
+ hmac_signature = HMAC::SHA256.digest(token.secret, string_to_sign)
48
+ # chomp because the Base64 output ends with \n
49
+ @auth_hash[:auth_signature] = Base64.encode64(hmac_signature).chomp
50
+
51
+ return @auth_hash
52
+ end
53
+
54
+ # Authenticates the request with a token
55
+ #
56
+ # Timestamp check: Unless timestamp_grace is set to nil (which will skip
57
+ # the timestamp check), an exception will be raised if timestamp is not
58
+ # supplied or if the timestamp provided is not within timestamp_grace of
59
+ # the real time (defaults to 10 minutes)
60
+ #
61
+ # Signature check: Raises an exception if the signature does not match the
62
+ # computed value
63
+ #
64
+ def authenticate_by_token!(token, timestamp_grace = 600)
65
+ validate_version!
66
+ validate_timestamp!(timestamp_grace)
67
+ validate_signature!(token)
68
+ true
69
+ end
70
+
71
+ def authenticate_by_token(token, timestamp_grace = 600)
72
+ authenticate_by_token!(token, timestamp_grace)
73
+ rescue AuthenticationError
74
+ false
75
+ end
76
+
77
+ def authenticate(timestamp_grace = 600, &block)
78
+ key = @auth_hash['auth_key']
79
+ raise AuthenticationError, "Authentication key required" unless key
80
+ token = yield key
81
+ unless token && token.secret
82
+ raise AuthenticationError, "Invalid authentication key"
83
+ end
84
+ authenticate_by_token!(token, timestamp_grace)
85
+ return token
86
+ end
87
+
88
+ def auth_hash
89
+ raise "Request not signed" unless @auth_hash && @auth_hash[:auth_signature]
90
+ @auth_hash
91
+ end
92
+
93
+ private
94
+
95
+ def string_to_sign
96
+ [@method, @path, parameter_string].join("\n")
97
+ end
98
+
99
+ def parameter_string
100
+ param_hash = @query_hash.merge(@auth_hash || {})
101
+
102
+ # Convert keys to lowercase strings
103
+ hash = {}; param_hash.each { |k,v| hash[k.to_s.downcase] = v }
104
+
105
+ # Exclude signature from signature generation!
106
+ hash.delete("auth_signature")
107
+
108
+ hash.keys.sort.map { |k| "#{k}=#{hash[k]}" }.join("&")
109
+ end
110
+
111
+ def validate_version!
112
+ version = @auth_hash["auth_version"]
113
+ raise AuthenticationError, "Version required" unless version
114
+ raise AuthenticationError, "Version not supported" unless version == '1.0'
115
+ end
116
+
117
+ def validate_timestamp!(grace)
118
+ return true if grace.nil?
119
+
120
+ timestamp = @auth_hash["auth_timestamp"]
121
+ error = (timestamp.to_i - Time.now.to_i).abs
122
+ raise AuthenticationError, "Timestamp required" unless timestamp
123
+ if error >= grace
124
+ raise AuthenticationError, "Timestamp expired: Given timestamp "\
125
+ "(#{Time.at(timestamp.to_i).utc.strftime(ISO8601)}) "\
126
+ "not within #{grace}s of server time "\
127
+ "(#{Time.now.utc.strftime(ISO8601)})"
128
+ end
129
+ return true
130
+ end
131
+
132
+ def validate_signature!(token)
133
+ string = string_to_sign
134
+ hmac_signature = HMAC::SHA256.digest(token.secret, string)
135
+ # chomp because the Base64 output ends with \n
136
+ base64_signature = Base64.encode64(hmac_signature).chomp
137
+ unless @auth_hash["auth_signature"] == base64_signature
138
+ raise AuthenticationError, "Invalid signature: you should have "\
139
+ "sent Base64Encode(HmacSHA256(#{string.inspect}, your_secret_key))"
140
+ end
141
+ return true
142
+ end
143
+ end
144
+ end
@@ -1,24 +1,62 @@
1
+ require 'crack/core_extensions' # Used for Hash#to_params
2
+ require 'digest/md5'
3
+
1
4
  module Pusher
2
5
  class Channel
3
- def initialize(key, id)
4
- @uri = URI.parse("http://#{Pusher.host}:#{Pusher.port}/app/#{key}/channel/#{id}")
6
+ def initialize(app_id, name)
7
+ @uri = URI::HTTP.build({
8
+ :host => Pusher.host,
9
+ :port => Pusher.port,
10
+ :path => "/apps/#{app_id}/channels/#{name}/events"
11
+ })
5
12
  @http = Net::HTTP.new(@uri.host, @uri.port)
6
13
  end
7
14
 
8
- def trigger(event_name, data, socket_id = nil)
9
- begin
10
- @http.post(
11
- @uri.path,
12
- self.class.turn_into_json({
13
- :event => event_name,
14
- :data => data,
15
- :socket_id => socket_id
16
- }),
17
- {'Content-Type'=> 'application/json'}
18
- )
19
- rescue StandardError => e
20
- handle_error e
15
+ def trigger!(event_name, data, socket_id = nil)
16
+ params = {
17
+ :name => event_name,
18
+ }
19
+ params[:socket_id] = socket_id if socket_id
20
+
21
+ body = case data
22
+ when String
23
+ data
24
+ else
25
+ begin
26
+ self.class.turn_into_json(data)
27
+ rescue => e
28
+ Pusher.logger.error("Could not convert #{data.inspect} into JSON")
29
+ raise e
30
+ end
21
31
  end
32
+ params[:body_md5] = Digest::MD5.hexdigest(body)
33
+
34
+ request = Authentication::Request.new('POST', @uri.path, params)
35
+ auth_hash = request.sign(Pusher.authentication_token)
36
+
37
+ query_params = params.merge(auth_hash)
38
+ @uri.query = query_params.to_params
39
+
40
+ response = @http.post("#{@uri.path}?#{@uri.query}", body, {
41
+ 'Content-Type'=> 'application/json'
42
+ })
43
+
44
+ case response.code
45
+ when "202"
46
+ return true
47
+ when "401"
48
+ raise AuthenticationError, response.body.chomp
49
+ when "404"
50
+ raise Error, "Resource not found: app_id is probably invalid"
51
+ else
52
+ raise Error, "Unknown error in Pusher: #{response.body.chomp}"
53
+ end
54
+ end
55
+
56
+ def trigger(event_name, data, socket_id = nil)
57
+ trigger!(event_name, data, socket_id)
58
+ rescue StandardError => e
59
+ handle_error e
22
60
  end
23
61
 
24
62
  def self.turn_into_json(data)
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.3.5"
8
+ s.version = "0.4.0.beta"
9
9
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
10
+ s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["New Bamboo"]
12
- s.date = %q{2010-04-15}
12
+ s.date = %q{2010-04-22}
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 = [
@@ -24,8 +24,10 @@ Gem::Specification.new do |s|
24
24
  "Rakefile",
25
25
  "VERSION",
26
26
  "lib/pusher.rb",
27
+ "lib/pusher/authentication.rb",
27
28
  "lib/pusher/channel.rb",
28
29
  "pusher.gemspec",
30
+ "spec/authentication_spec.rb",
29
31
  "spec/pusher_spec.rb",
30
32
  "spec/spec.opts",
31
33
  "spec/spec_helper.rb"
@@ -36,7 +38,8 @@ Gem::Specification.new do |s|
36
38
  s.rubygems_version = %q{1.3.6}
37
39
  s.summary = %q{Pusher App client}
38
40
  s.test_files = [
39
- "spec/pusher_spec.rb",
41
+ "spec/authentication_spec.rb",
42
+ "spec/pusher_spec.rb",
40
43
  "spec/spec_helper.rb"
41
44
  ]
42
45
 
@@ -46,14 +49,20 @@ Gem::Specification.new do |s|
46
49
 
47
50
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
51
  s.add_runtime_dependency(%q<json>, [">= 0"])
52
+ s.add_runtime_dependency(%q<crack>, [">= 0"])
49
53
  s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
54
+ s.add_development_dependency(%q<webmock>, [">= 0"])
50
55
  else
51
56
  s.add_dependency(%q<json>, [">= 0"])
57
+ s.add_dependency(%q<crack>, [">= 0"])
52
58
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
59
+ s.add_dependency(%q<webmock>, [">= 0"])
53
60
  end
54
61
  else
55
62
  s.add_dependency(%q<json>, [">= 0"])
63
+ s.add_dependency(%q<crack>, [">= 0"])
56
64
  s.add_dependency(%q<rspec>, [">= 1.2.9"])
65
+ s.add_dependency(%q<webmock>, [">= 0"])
57
66
  end
58
67
  end
59
68
 
@@ -0,0 +1,176 @@
1
+ require File.expand_path('../spec_helper', __FILE__)
2
+
3
+ describe Authentication do
4
+ before :each do
5
+ Time.stub!(:now).and_return(Time.at(1234))
6
+
7
+ @token = Authentication::Token.new('key', 'secret')
8
+
9
+ @request = Authentication::Request.new('POST', '/some/path', {
10
+ "query" => "params",
11
+ "go" => "here"
12
+ })
13
+ @signature = @request.sign(@token)[:auth_signature]
14
+ end
15
+
16
+ it "should generate base64 encoded signature from correct key" do
17
+ @request.send(:string_to_sign).should == "POST\n/some/path\nauth_key=key&auth_timestamp=1234&auth_version=1.0&go=here&query=params"
18
+ @signature.should == 'OyN5U6W6ZhmHXLsqLUPo2p71gk6KLGifYoSshbweoNs='
19
+ end
20
+
21
+ it "should make auth_hash available after request is signed" do
22
+ request = Authentication::Request.new('POST', '/some/path', {
23
+ "query" => "params"
24
+ })
25
+ lambda {
26
+ request.auth_hash
27
+ }.should raise_error('Request not signed')
28
+
29
+ request.sign(@token)
30
+ request.auth_hash.should == {
31
+ :auth_signature => "2gePzt1ylBtshzyqQNDWsgAOv8cAzugCsSjdIPcudOk=",
32
+ :auth_version => "1.0",
33
+ :auth_key => "key",
34
+ :auth_timestamp => 1234
35
+ }
36
+ end
37
+
38
+ it "should cope with symbol keys" do
39
+ @request.query_hash = {
40
+ :query => "params",
41
+ :go => "here"
42
+ }
43
+ @request.sign(@token)[:auth_signature].should == @signature
44
+ end
45
+
46
+ it "should cope with upcase keys (keys are lowercased before signing)" do
47
+ @request.query_hash = {
48
+ "Query" => "params",
49
+ "GO" => "here"
50
+ }
51
+ @request.sign(@token)[:auth_signature].should == @signature
52
+ end
53
+
54
+ it "should use the path to generate signature" do
55
+ @request.path = '/some/other/path'
56
+ @request.sign(@token)[:auth_signature].should_not == @signature
57
+ end
58
+
59
+ it "should use the query string keys to generate signature" do
60
+ @request.query_hash = {
61
+ "other" => "query"
62
+ }
63
+ @request.sign(@token)[:auth_signature].should_not == @signature
64
+ end
65
+
66
+ it "should use the query string values to generate signature" do
67
+ @request.query_hash = {
68
+ "key" => "notfoo",
69
+ "other" => 'bar'
70
+ }
71
+ @request.sign(@token)[:signature].should_not == @signature
72
+ end
73
+
74
+ describe "verification" do
75
+ before :each do
76
+ Time.stub!(:now).and_return(Time.at(1234))
77
+ @request.sign(@token)
78
+ @params = @request.query_hash.merge(@request.auth_hash)
79
+ end
80
+
81
+ it "should verify requests" do
82
+ request = Authentication::Request.new('POST', '/some/path', @params)
83
+ request.authenticate_by_token(@token).should == true
84
+ end
85
+
86
+ it "should raise error if signature is not correct" do
87
+ @params[:auth_signature] = 'asdf'
88
+ request = Authentication::Request.new('POST', '/some/path', @params)
89
+ lambda {
90
+ request.authenticate_by_token!(@token)
91
+ }.should raise_error('Invalid signature: you should have sent Base64Encode(HmacSHA256("POST\n/some/path\nauth_key=key&auth_timestamp=1234&auth_version=1.0&go=here&query=params", your_secret_key))')
92
+ end
93
+
94
+ it "should raise error if timestamp not available" do
95
+ @params.delete(:auth_timestamp)
96
+ request = Authentication::Request.new('POST', '/some/path', @params)
97
+ lambda {
98
+ request.authenticate_by_token!(@token)
99
+ }.should raise_error('Timestamp required')
100
+ end
101
+
102
+ it "should raise error if timestamp has expired (default of 600s)" do
103
+ request = Authentication::Request.new('POST', '/some/path', @params)
104
+ Time.stub!(:now).and_return(Time.at(1234 + 599))
105
+ request.authenticate_by_token!(@token).should == true
106
+ Time.stub!(:now).and_return(Time.at(1234 - 599))
107
+ request.authenticate_by_token!(@token).should == true
108
+ Time.stub!(:now).and_return(Time.at(1234 + 600))
109
+ lambda {
110
+ request.authenticate_by_token!(@token)
111
+ }.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 600s of server time (1970-01-01T00:30:34Z)")
112
+ Time.stub!(:now).and_return(Time.at(1234 - 600))
113
+ lambda {
114
+ request.authenticate_by_token!(@token)
115
+ }.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 600s of server time (1970-01-01T00:10:34Z)")
116
+ end
117
+
118
+ it "should be possible to customize the timeout grace period" do
119
+ grace = 10
120
+ request = Authentication::Request.new('POST', '/some/path', @params)
121
+ Time.stub!(:now).and_return(Time.at(1234 + grace - 1))
122
+ request.authenticate_by_token!(@token, grace).should == true
123
+ Time.stub!(:now).and_return(Time.at(1234 + grace))
124
+ lambda {
125
+ request.authenticate_by_token!(@token, grace)
126
+ }.should raise_error("Timestamp expired: Given timestamp (1970-01-01T00:20:34Z) not within 10s of server time (1970-01-01T00:20:44Z)")
127
+ end
128
+
129
+ it "should be possible to skip timestamp check by passing nil" do
130
+ request = Authentication::Request.new('POST', '/some/path', @params)
131
+ Time.stub!(:now).and_return(Time.at(1234 + 1000))
132
+ request.authenticate_by_token!(@token, nil).should == true
133
+ end
134
+
135
+ it "should check that auth_version is supplied" do
136
+ @params.delete(:auth_version)
137
+ request = Authentication::Request.new('POST', '/some/path', @params)
138
+ lambda {
139
+ request.authenticate_by_token!(@token)
140
+ }.should raise_error('Version required')
141
+ end
142
+
143
+ it "should check that auth_version equals 1.0" do
144
+ @params[:auth_version] = '1.1'
145
+ request = Authentication::Request.new('POST', '/some/path', @params)
146
+ lambda {
147
+ request.authenticate_by_token!(@token)
148
+ }.should raise_error('Version not supported')
149
+ end
150
+
151
+ describe "when used with optional block" do
152
+ it "should optionally take a block which yields the signature" do
153
+ request = Authentication::Request.new('POST', '/some/path', @params)
154
+ request.authenticate do |key|
155
+ key.should == @token.key
156
+ @token
157
+ end.should == @token
158
+ end
159
+
160
+ it "should raise error if no auth_key supplied to request" do
161
+ @params.delete(:auth_key)
162
+ request = Authentication::Request.new('POST', '/some/path', @params)
163
+ lambda {
164
+ request.authenticate { |key| nil }
165
+ }.should raise_error('Authentication key required')
166
+ end
167
+
168
+ it "should raise error if block returns nil (i.e. key doesn't exist)" do
169
+ request = Authentication::Request.new('POST', '/some/path', @params)
170
+ lambda {
171
+ request.authenticate { |key| nil }
172
+ }.should raise_error('Invalid authentication key')
173
+ end
174
+ end
175
+ end
176
+ end
data/spec/pusher_spec.rb CHANGED
@@ -1,5 +1,7 @@
1
1
  require File.expand_path('../spec_helper', __FILE__)
2
2
 
3
+ require 'webmock/rspec'
4
+
3
5
  describe Pusher do
4
6
  describe 'configuration' do
5
7
  it 'should be preconfigured for api host' do
@@ -22,27 +24,17 @@ describe Pusher do
22
24
  Pusher.logger.debug('foo')
23
25
  Pusher.logger = nil
24
26
  end
25
-
26
- it 'should raise exception if key and secret are missing' do
27
- lambda {
28
- Pusher['test-channel']
29
- }.should raise_error(Pusher::ArgumentError)
30
- end
31
-
32
- it 'should raise exception if key is missing' do
33
- lambda {
34
- Pusher['test-channel']
35
- }.should raise_error(Pusher::ArgumentError)
36
- end
37
27
  end
38
28
 
39
29
  describe 'configured' do
40
30
  before do
31
+ Pusher.app_id = '20'
41
32
  Pusher.key = '12345678900000001'
42
33
  Pusher.secret = '12345678900000001'
43
34
  end
44
35
 
45
36
  after do
37
+ Pusher.app_id = nil
46
38
  Pusher.key = nil
47
39
  Pusher.secret = nil
48
40
  end
@@ -55,34 +47,102 @@ describe Pusher do
55
47
  it 'should return a new channel' do
56
48
  @channel.should be_kind_of(Pusher::Channel)
57
49
  end
50
+
51
+ %w{app_id key secret}.each do |config|
52
+ it "should raise exception if #{config} not configured" do
53
+ Pusher.send("#{config}=", nil)
54
+ lambda {
55
+ Pusher['test_channel']
56
+ }.should raise_error(ArgumentError)
57
+ end
58
+ end
58
59
  end
59
60
 
60
- describe 'Channel#trigger' do
61
- before do
62
- @http = mock('HTTP', :post => 'posting')
63
- Net::HTTP.stub!(:new).and_return @http
61
+ describe 'Channel#trigger!' do
62
+ before :each do
63
+ WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event})
64
+ @channel = Pusher['test_channel']
64
65
  end
65
66
 
66
67
  it 'should configure HTTP library to talk to pusher API' do
67
- Net::HTTP.should_receive(:new).
68
- with('api.pusherapp.com', 80).and_return @http
69
- Pusher['test_channel'].trigger('new_event', 'Some data')
68
+ @channel.trigger!('new_event', 'Some data')
69
+ WebMock.request(:post, %r{api.pusherapp.com}).should have_been_made
70
70
  end
71
71
 
72
- it 'should POST JSON to pusher API' do
73
- @http.should_receive(:post) do |path, data, headers|
74
- path.should == '/app/12345678900000001/channel/test_channel'
75
- parsed = JSON.parse(data)
76
- parsed['event'].should == 'new_event'
77
- parsed['data']['name'].should == 'Pusher'
78
- parsed['data']['last_name'].should == 'App'
79
- parsed['socket_id'].should == nil
80
- headers.should == {'Content-Type'=> 'application/json'}
81
- end
82
- Pusher['test_channel'].trigger('new_event', {
72
+ it 'should POST with the correct parameters and convert data to JSON' do
73
+ @channel.trigger!('new_event', {
83
74
  :name => 'Pusher',
84
75
  :last_name => 'App'
85
76
  })
77
+ WebMock.request(:post, %r{/app/20/channel/test_channel/event}).
78
+ with do |req|
79
+
80
+ query_hash = req.uri.query_values
81
+ query_hash["name"].should == 'new_event'
82
+ query_hash["auth_key"].should == Pusher.key
83
+ query_hash["auth_timestamp"].should_not be_nil
84
+
85
+ parsed = JSON.parse(req.body)
86
+ parsed.should == {
87
+ "name" => 'Pusher',
88
+ "last_name" => 'App'
89
+ }
90
+
91
+ req.headers['Content-Type'].should == 'application/json'
92
+ end.should have_been_made
93
+ end
94
+
95
+ it "should handle string data by sending unmodified in body" do
96
+ string = "foo\nbar\""
97
+ @channel.trigger!('new_event', string)
98
+ WebMock.request(:post, %r{/app/20/channel/test_channel/event}).with do |req|
99
+ req.body.should == "foo\nbar\""
100
+ end.should have_been_made
101
+ end
102
+
103
+ it "should raise error if an object sent which canot be JSONified" do
104
+ lambda {
105
+ @channel.trigger!('new_event', Object.new)
106
+ }.should raise_error(JSON::GeneratorError)
107
+ end
108
+
109
+ it "should propagate exception if exception raised" do
110
+ WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
111
+ to_raise(RuntimeError)
112
+ lambda {
113
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
114
+ }.should raise_error(RuntimeError)
115
+ end
116
+
117
+ it "should raise AuthenticationError if pusher returns 401" do
118
+ WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
119
+ to_return(:status => 401)
120
+ lambda {
121
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
122
+ }.should raise_error(Pusher::AuthenticationError)
123
+ end
124
+
125
+ it "should raise Pusher::Error if pusher returns 404" do
126
+ WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
127
+ to_return(:status => 404)
128
+ lambda {
129
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
130
+ }.should raise_error(Pusher::Error, 'Resource not found: app_id is probably invalid')
131
+ end
132
+
133
+ it "should raise Pusher::Error if pusher returns 500" do
134
+ WebMock.stub_request(:post, %r{/app/20/channel/test_channel/event}).
135
+ to_return(:status => 500, :body => "some error")
136
+ lambda {
137
+ Pusher['test_channel'].trigger!('new_event', 'Some data')
138
+ }.should raise_error(Pusher::Error, 'Unknown error in Pusher: some error')
139
+ end
140
+ end
141
+
142
+ describe 'Channel#trigger' do
143
+ before :each do
144
+ @http = mock('HTTP', :post => 'posting')
145
+ Net::HTTP.stub!(:new).and_return @http
86
146
  end
87
147
 
88
148
  it "should log failure if exception raised" do
metadata CHANGED
@@ -1,12 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pusher
3
3
  version: !ruby/object:Gem::Version
4
- prerelease: false
4
+ prerelease: true
5
5
  segments:
6
6
  - 0
7
- - 3
8
- - 5
9
- version: 0.3.5
7
+ - 4
8
+ - 0
9
+ - beta
10
+ version: 0.4.0.beta
10
11
  platform: ruby
11
12
  authors:
12
13
  - New Bamboo
@@ -14,7 +15,7 @@ autorequire:
14
15
  bindir: bin
15
16
  cert_chain: []
16
17
 
17
- date: 2010-04-15 00:00:00 +01:00
18
+ date: 2010-04-22 00:00:00 +01:00
18
19
  default_executable:
19
20
  dependencies:
20
21
  - !ruby/object:Gem::Dependency
@@ -30,9 +31,21 @@ dependencies:
30
31
  type: :runtime
31
32
  version_requirements: *id001
32
33
  - !ruby/object:Gem::Dependency
33
- name: rspec
34
+ name: crack
34
35
  prerelease: false
35
36
  requirement: &id002 !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ segments:
41
+ - 0
42
+ version: "0"
43
+ type: :runtime
44
+ version_requirements: *id002
45
+ - !ruby/object:Gem::Dependency
46
+ name: rspec
47
+ prerelease: false
48
+ requirement: &id003 !ruby/object:Gem::Requirement
36
49
  requirements:
37
50
  - - ">="
38
51
  - !ruby/object:Gem::Version
@@ -42,7 +55,19 @@ dependencies:
42
55
  - 9
43
56
  version: 1.2.9
44
57
  type: :development
45
- version_requirements: *id002
58
+ version_requirements: *id003
59
+ - !ruby/object:Gem::Dependency
60
+ name: webmock
61
+ prerelease: false
62
+ requirement: &id004 !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ segments:
67
+ - 0
68
+ version: "0"
69
+ type: :development
70
+ version_requirements: *id004
46
71
  description: Wrapper for pusherapp.com REST api
47
72
  email: support@pusherapp.com
48
73
  executables: []
@@ -60,8 +85,10 @@ files:
60
85
  - Rakefile
61
86
  - VERSION
62
87
  - lib/pusher.rb
88
+ - lib/pusher/authentication.rb
63
89
  - lib/pusher/channel.rb
64
90
  - pusher.gemspec
91
+ - spec/authentication_spec.rb
65
92
  - spec/pusher_spec.rb
66
93
  - spec/spec.opts
67
94
  - spec/spec_helper.rb
@@ -83,11 +110,13 @@ required_ruby_version: !ruby/object:Gem::Requirement
83
110
  version: "0"
84
111
  required_rubygems_version: !ruby/object:Gem::Requirement
85
112
  requirements:
86
- - - ">="
113
+ - - ">"
87
114
  - !ruby/object:Gem::Version
88
115
  segments:
89
- - 0
90
- version: "0"
116
+ - 1
117
+ - 3
118
+ - 1
119
+ version: 1.3.1
91
120
  requirements: []
92
121
 
93
122
  rubyforge_project:
@@ -96,5 +125,6 @@ signing_key:
96
125
  specification_version: 3
97
126
  summary: Pusher App client
98
127
  test_files:
128
+ - spec/authentication_spec.rb
99
129
  - spec/pusher_spec.rb
100
130
  - spec/spec_helper.rb