pusher 0.3.5 → 0.4.0.beta

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