pling 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/pling/message.rb CHANGED
@@ -12,6 +12,58 @@ module Pling
12
12
  body &&= body.to_s
13
13
  @body = body
14
14
  end
15
+
16
+ ##
17
+ # The message subject - not supported by all gateways
18
+ #
19
+ # @overload subject
20
+ # @overload subject=(subject)
21
+ # @param [#to_s] subject
22
+ attr_reader :subject
23
+
24
+ def subject=(subject)
25
+ subject &&= subject.to_s
26
+ @subject = subject
27
+ end
28
+
29
+ ##
30
+ # The message badge - not supported by all gateways
31
+ #
32
+ # @overload badge
33
+ # @overload badge=(badge)
34
+ # @param [#to_s] badge
35
+ attr_reader :badge
36
+
37
+ def badge=(badge)
38
+ badge &&= badge.to_s
39
+ @badge = badge
40
+ end
41
+
42
+ ##
43
+ # The message sound - not supported by all gateways
44
+ #
45
+ # @overload sound
46
+ # @overload sound=(sound)
47
+ # @param [#to_s] sound
48
+ attr_reader :sound
49
+
50
+ def sound=(sound)
51
+ sound &&= sound.to_s
52
+ @sound = sound
53
+ end
54
+
55
+ ##
56
+ # The message payload - not supported by all gateways
57
+ #
58
+ # @overload payload
59
+ # @overload payload=(sound)
60
+ # @param [#to_hash] payload
61
+ attr_reader :payload
62
+
63
+ def payload=(payload)
64
+ payload &&= payload.to_hash
65
+ @payload = payload
66
+ end
15
67
 
16
68
  ##
17
69
  # Creates a new Pling::Message instance with the given body
@@ -1,12 +1,43 @@
1
1
  module Pling
2
2
  module Middleware
3
+ ##
4
+ # This is the base class to implement custom middleware for pling.
5
+ #
6
+ # Middleware should inherit from this base class and implement a {#deliver} method.
7
+ # To call the next middleware on the stack this method must yield passing the given
8
+ # message and device.
9
+ #
10
+ # @example
11
+ #
12
+ # class Pling::Middleware::TimeFilter < Pling::Middleware::Base
13
+ # def deliver(message, device)
14
+ # yield(message, device) if configuration[:range].include? Time.now.hour
15
+ # end
16
+ #
17
+ # protected
18
+ #
19
+ # def default_configuration
20
+ # super.merge({
21
+ # :range => 8..22
22
+ # })
23
+ # end
24
+ # end
3
25
  class Base
4
26
  include Pling::Configurable
5
27
 
28
+ ##
29
+ # Initializes a new middleware instance
30
+ #
31
+ # @param [Hash] configuration
6
32
  def initialize(configuration = {})
7
33
  setup_configuration(configuration)
8
34
  end
9
35
 
36
+ ##
37
+ # Processes the given message and device and passes it to the next
38
+ # middleware on the stack.
39
+ #
40
+ # @yield [message, device] Call the next middleware on the stack
10
41
  def deliver(message, device)
11
42
  yield(message, device)
12
43
  end
data/lib/pling/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Pling
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
data/lib/pling.rb CHANGED
@@ -1,10 +1,13 @@
1
- require "pling/version"
1
+ require 'pling/version'
2
+ require 'logger'
2
3
 
3
4
  module Pling
4
5
 
5
6
  autoload :Device, 'pling/device'
6
7
  autoload :Message, 'pling/message'
7
8
  autoload :Gateway, 'pling/gateway'
9
+ autoload :APN, 'pling/apn'
10
+ autoload :C2DM, 'pling/c2dm'
8
11
  autoload :Middleware, 'pling/middleware'
9
12
  autoload :Adapter, 'pling/adapter'
10
13
  autoload :Configurable, 'pling/configurable'
@@ -13,12 +16,23 @@ module Pling
13
16
  @gateways = Pling::DelayedInitializer.new
14
17
  @middlewares = Pling::DelayedInitializer.new
15
18
  @adapter = Pling::Adapter::Base.new
19
+ @logger = Logger.new(nil)
16
20
 
17
21
  class Error < StandardError; end
18
22
  class AuthenticationFailed < Error; end
19
- class DeliveryFailed < Error; end
20
23
  class NoGatewayFound < Error; end
21
24
 
25
+ class DeliveryFailed < Error
26
+ attr_reader :pling_message, :pling_device
27
+
28
+ def initialize(message = nil, pling_message = nil, pling_device = nil)
29
+ super(message)
30
+ @pling_message = pling_message
31
+ @pling_device = pling_device
32
+ end
33
+ end
34
+
35
+
22
36
  class << self
23
37
  ##
24
38
  # Stores the list of available gateway instances
@@ -46,6 +60,12 @@ module Pling
46
60
  # @return [Pling::Adapter]
47
61
  attr_accessor :adapter
48
62
 
63
+ ##
64
+ # Stores the logger. Defaults to Logger.new(nil)
65
+ #
66
+ # @return [Logger]
67
+ attr_accessor :logger
68
+
49
69
  ##
50
70
  # Allows configuration of Pling by passing a config object to the given block
51
71
  #
@@ -66,6 +86,8 @@ module Pling
66
86
  message = Pling._convert(message, :message)
67
87
  device = Pling._convert(device, :device)
68
88
 
89
+ Pling.logger.info "#{self.class} -- Delivering #{message.inspect} to #{device.inspect}"
90
+
69
91
  stack ||= middlewares.initialize! + [adapter]
70
92
 
71
93
  stack.shift.deliver(message, device) do |m, d|
data/pling.gemspec CHANGED
@@ -7,7 +7,7 @@ Gem::Specification.new do |s|
7
7
  s.version = Pling::VERSION
8
8
  s.authors = ["Benedikt Deicke", "Konstantin Tennhard", "Christian Bäuerlein"]
9
9
  s.email = ["benedikt@synatic.net", "me@t6d.de", "fabrik42@gmail.com"]
10
- s.homepage = "https://flinc.github.com/pling"
10
+ s.homepage = "http://flinc.github.com/pling"
11
11
  s.summary = %q{Pling is a notification framework that supports multiple gateways}
12
12
  s.description = %q{Pling is a notification framework that supports multiple gateways. Currently supported are Android Push and SMS.}
13
13
 
Binary file
Binary file
@@ -0,0 +1,124 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::APN::Connection do
4
+
5
+ let(:configuration) do
6
+ {
7
+ :host => "localhost",
8
+ :port => 2195,
9
+ :certificate => "/path/to/certificate.pem"
10
+ }
11
+ end
12
+
13
+ let(:ssl_context) { double(OpenSSL::SSL::SSLContext).as_null_object }
14
+ let(:x509_certificate) { double(OpenSSL::X509::Certificate).as_null_object }
15
+ let(:pkey_rsa) { double(OpenSSL::PKey::RSA).as_null_object }
16
+ let(:tcp_socket) { double(TCPSocket).as_null_object }
17
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket, :closed? => false).as_null_object }
18
+
19
+ before do
20
+ File.stub(:read).and_return('')
21
+ OpenSSL::SSL::SSLContext.stub(:new).and_return(ssl_context)
22
+ OpenSSL::X509::Certificate.stub(:new).and_return(x509_certificate)
23
+ OpenSSL::PKey::RSA.stub(:new).and_return(pkey_rsa)
24
+ TCPSocket.stub(:new).and_return(tcp_socket)
25
+ OpenSSL::SSL::SSLSocket.stub(:new).and_return(ssl_socket)
26
+ end
27
+
28
+ context "when creating it from a valid configuration" do
29
+
30
+ it "should read the certificate" do
31
+ File.should_receive(:read).
32
+ with('/path/to/certificate.pem').
33
+ and_return("--- CERT CONTENT ---")
34
+ end
35
+
36
+ it "should create a tcp socket" do
37
+ TCPSocket.should_receive(:new).with("localhost", 2195)
38
+ end
39
+
40
+ it "should create a ssl socket" do
41
+ OpenSSL::SSL::SSLSocket.should_receive(:new).with(tcp_socket, ssl_context)
42
+ end
43
+
44
+ it "should create a ssl context" do
45
+ OpenSSL::SSL::SSLContext.should_receive(:new).and_return(ssl_context)
46
+ ssl_context.should_receive(:cert=).with(x509_certificate)
47
+ ssl_context.should_receive(:key=).with(pkey_rsa)
48
+ end
49
+
50
+ it "should connect the ssl socket" do
51
+ ssl_socket.should_receive(:connect)
52
+ end
53
+
54
+ after do
55
+ Pling::APN::Connection.new(configuration)
56
+ end
57
+
58
+ end
59
+
60
+ context 'when created with an invalid configuration' do
61
+
62
+ it "should raise an error when :certificate is missing" do
63
+ expect { Pling::APN::Connection.new({}) }.to raise_error(ArgumentError, /:certificate is missing/)
64
+ end
65
+
66
+ end
67
+
68
+ context "when writing data" do
69
+
70
+ subject do
71
+ Pling::APN::Connection.new(configuration)
72
+ end
73
+
74
+ it "should simply pass on the data to the underlying SSL socket" do
75
+ data = 'Pass this through!'
76
+ ssl_socket.should_receive(:write).with(data)
77
+ subject.write(data)
78
+ end
79
+
80
+ it "should raise an exception it is closed" do
81
+ ssl_socket.stub(:closed? => true)
82
+ subject.close
83
+ expect { subject.write("Waahhhh!") }.to raise_error(IOError, "Connection closed")
84
+ end
85
+
86
+ it "should retry three times on Errno::EPIPE" do
87
+ ssl_socket.should_receive(:write).exactly(3).times.and_raise(Errno::EPIPE)
88
+ expect { subject.write("Whoops!") }.to raise_error(IOError)
89
+ end
90
+
91
+ it 'should reconnect between retries' do
92
+ ssl_socket.stub(:write).and_raise(Errno::EPIPE)
93
+ ssl_socket.should_receive(:connect).exactly(3).times
94
+ ssl_socket.should_receive(:close).exactly(2).times
95
+ expect { subject.write("Whoops!") }.to raise_error(IOError)
96
+ end
97
+
98
+ end
99
+
100
+ describe "#gets" do
101
+ subject do
102
+ Pling::APN::Connection.new(configuration)
103
+ end
104
+
105
+ it "should simply pass on the data" do
106
+ data = 'Pass this through!'
107
+ ssl_socket.should_receive(:gets).and_return(data)
108
+ subject.gets.should eq(data)
109
+ end
110
+ end
111
+
112
+ describe "#read" do
113
+ subject do
114
+ Pling::APN::Connection.new(configuration)
115
+ end
116
+
117
+ it "should simply pass on the data" do
118
+ data = 'Pass this through!'
119
+ ssl_socket.should_receive(:read).and_return(data)
120
+ subject.read.should eq(data)
121
+ end
122
+ end
123
+
124
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::APN::Feedback do
4
+
5
+ let(:connection) { double(Pling::APN::Connection, :closed? => false).as_null_object }
6
+
7
+ let(:time) { Time.now.to_i }
8
+
9
+ # Simulate device tokens of _different_ length! A device token is _not_
10
+ # necessarily 32 byte long. Check the documentation if you don't belive it. :)
11
+ # http://bit.ly/apple-apn-feedback-documentation
12
+ let(:token_0) { "0" * 64 }
13
+ let(:token_1) { "00000000000000000000000000000001" }
14
+
15
+ let(:feedback_0) { [time, 32, token_0].pack("NnH*") }
16
+ let(:feedback_1) { [time, 16, token_1].pack("NnH*") }
17
+
18
+ subject do
19
+ described_class.new(:certificate => '/path/to/certificate.pem')
20
+ end
21
+
22
+ before do
23
+ Pling::APN::Connection.stub(:new).and_return(connection)
24
+ connection.stub(:gets).and_return(nil)
25
+ end
26
+
27
+ it { should respond_to(:get) }
28
+
29
+ context "when getting feedback" do
30
+
31
+ it "should be in form of a list" do
32
+ subject.get.should be_kind_of(Array)
33
+ end
34
+
35
+ it "should contain all tokens send by Apple" do
36
+ connection.stub(:gets).and_return(feedback_0, feedback_1, nil)
37
+ tokens = subject.get
38
+
39
+ tokens.should be == [token_0, token_1]
40
+ end
41
+
42
+ end
43
+
44
+ end
@@ -0,0 +1,141 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::APN::Gateway do
4
+
5
+ let(:valid_configuration) { { :certificate => '/path/to/certificate.pem' } }
6
+
7
+ let(:message) { Pling::Message.new('Hello from Pling') }
8
+
9
+ let(:device) { Pling::Device.new(:identifier => '0' * 64, :type => :iphone) }
10
+
11
+ let(:connection) { double(Pling::APN::Connection).as_null_object }
12
+
13
+ before do
14
+ Pling::APN::Connection.stub(:new).and_return(connection)
15
+ end
16
+
17
+ it 'should handle various apn related device types' do
18
+ Pling::APN::Gateway.handled_types.should =~ [:apple, :apn, :ios, :ipad, :iphone, :ipod]
19
+ end
20
+
21
+ describe '#deliver' do
22
+ subject { Pling::APN::Gateway.new(valid_configuration) }
23
+
24
+ it 'should raise an error if no message is given' do
25
+ expect { subject.deliver(nil, device) }.to raise_error
26
+ end
27
+
28
+ it 'should raise an error the device is given' do
29
+ expect { subject.deliver(message, nil) }.to raise_error
30
+ end
31
+
32
+ it 'should call #to_pling_message on the given message' do
33
+ message.should_receive(:to_pling_message).and_return(message)
34
+ subject.deliver(message, device)
35
+ end
36
+
37
+ it 'should call #to_pling_device on the given device' do
38
+ device.should_receive(:to_pling_device).and_return(device)
39
+ subject.deliver(message, device)
40
+ end
41
+
42
+ it 'should raise an exception when the payload exceeds 256 bytes' do
43
+ message.body = "X" * 256
44
+ expect { subject.deliver(message, device) }.to raise_error(Pling::DeliveryFailed, /Payload size of \d+ exceeds allowed size of 256 bytes/)
45
+ end
46
+
47
+ it 'should try to deliver the given message' do
48
+ expected_header = "\x00\x00 \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$"
49
+ expected_payload = {
50
+ 'aps' => {
51
+ 'alert' => 'Hello from Pling'
52
+ }
53
+ }
54
+
55
+ connection.stub(:write) do |packet|
56
+ header, payload = packet[0..36], packet[37..-1]
57
+ header.should eq(expected_header)
58
+ JSON.parse(payload).should eq(expected_payload)
59
+ end
60
+
61
+ subject.deliver(message, device)
62
+ end
63
+
64
+ it 'should include the given badge' do
65
+ expected_payload = {
66
+ 'aps' => {
67
+ 'alert' => 'Hello from Pling',
68
+ 'badge' => 10
69
+ }
70
+ }
71
+
72
+ connection.stub(:write) do |packet|
73
+ JSON.parse(packet[37..-1]).should eq(expected_payload)
74
+ end
75
+
76
+ message.badge = 10
77
+
78
+ subject.deliver(message, device)
79
+ end
80
+
81
+ it 'should include the given badge' do
82
+ expected_payload = {
83
+ 'aps' => {
84
+ 'alert' => 'Hello from Pling',
85
+ 'sound' => 'pling'
86
+ }
87
+ }
88
+
89
+ connection.stub(:write) do |packet|
90
+ JSON.parse(packet[37..-1]).should eq(expected_payload)
91
+ end
92
+
93
+ message.sound = :pling
94
+
95
+ subject.deliver(message, device)
96
+ end
97
+
98
+ context 'when configured to include payload' do
99
+ before do
100
+ valid_configuration.merge!(:payload => true)
101
+ message.payload = { :data => 'available' }
102
+ end
103
+
104
+ it 'should include the given payload' do
105
+ expected_payload = {
106
+ 'aps' => {
107
+ 'alert' => 'Hello from Pling'
108
+ },
109
+ 'data' => 'available'
110
+ }
111
+
112
+ connection.stub(:write) do |packet|
113
+ JSON.parse(packet[37..-1]).should eq(expected_payload)
114
+ end
115
+
116
+ subject.deliver(message, device)
117
+ end
118
+ end
119
+
120
+ context 'when configured to not include payload' do
121
+ before do
122
+ valid_configuration.merge!(:payload => false)
123
+ message.payload = { :data => 'available' }
124
+ end
125
+
126
+ it 'should not include the given payload' do
127
+ expected_payload = {
128
+ 'aps' => {
129
+ 'alert' => 'Hello from Pling'
130
+ }
131
+ }
132
+
133
+ connection.stub(:write) do |packet|
134
+ JSON.parse(packet[37..-1]).should eq(expected_payload)
135
+ end
136
+
137
+ subject.deliver(message, device)
138
+ end
139
+ end
140
+ end
141
+ end
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe Pling::Gateway::C2DM do
3
+ describe Pling::C2DM::Gateway do
4
4
 
5
5
  let(:valid_configuration) do
6
6
  { :email => 'someone@gmail.com', :password => 'random', :source => 'some-source' }
@@ -28,12 +28,16 @@ describe Pling::Gateway::C2DM do
28
28
 
29
29
  before { Faraday.stub(:new).and_return(connection_mock) }
30
30
 
31
+ it 'should handle various apn related device types' do
32
+ Pling::C2DM::Gateway.handled_types.should =~ [:android, :c2dm]
33
+ end
34
+
31
35
  context 'when created with an invalid configuration' do
32
36
  [:email, :password, :source].each do |attribute|
33
37
  it "should raise an error when :#{attribute} is missing" do
34
38
  configuration = valid_configuration
35
39
  configuration.delete(attribute)
36
- expect { Pling::Gateway::C2DM.new(configuration) }.to raise_error(ArgumentError, /:#{attribute} is missing/)
40
+ expect { Pling::C2DM::Gateway.new(configuration) }.to raise_error(ArgumentError, /:#{attribute} is missing/)
37
41
  end
38
42
  end
39
43
  end
@@ -52,7 +56,7 @@ describe Pling::Gateway::C2DM do
52
56
  end
53
57
 
54
58
  it 'should not raise an error' do
55
- expect { Pling::Gateway::C2DM.new(valid_configuration) }.to_not raise_error
59
+ expect { Pling::C2DM::Gateway.new(valid_configuration) }.to_not raise_error
56
60
  end
57
61
 
58
62
  it 'should try to authenticate' do
@@ -60,31 +64,31 @@ describe Pling::Gateway::C2DM do
60
64
  with('https://www.google.com/accounts/ClientLogin', valid_authentication_params).
61
65
  and_return(authentication_response_mock)
62
66
 
63
- Pling::Gateway::C2DM.new(valid_configuration)
67
+ Pling::C2DM::Gateway.new(valid_configuration)
64
68
  end
65
69
 
66
70
  it 'should extract the token from the response body' do
67
- gateway = Pling::Gateway::C2DM.new(valid_configuration)
71
+ gateway = Pling::C2DM::Gateway.new(valid_configuration)
68
72
  gateway.token.should eq('S0ME-ToKeN123')
69
73
  end
70
74
 
71
75
  it 'should raise an error if authentication was not successful' do
72
76
  authentication_response_mock.stub(:status => 403, :success? => false, :body => 'Error=BadAuthentication')
73
77
 
74
- expect { Pling::Gateway::C2DM.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Authentication failed: \[403\] Error=BadAuthentication/)
78
+ expect { Pling::C2DM::Gateway.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Authentication failed: \[403\] Error=BadAuthentication/)
75
79
  end
76
80
 
77
81
  it 'should raise an error if it could not extract a token from the response' do
78
82
  authentication_response_mock.stub(:body).and_return('SOMERANDOMBODY')
79
83
 
80
- expect { Pling::Gateway::C2DM.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Token extraction failed/)
84
+ expect { Pling::C2DM::Gateway.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Token extraction failed/)
81
85
  end
82
86
  end
83
87
 
84
88
  context 'configuration' do
85
89
  it 'should allow configuring Faraday\'s :connection settings' do
86
90
  Faraday.should_receive(:new).with(:ssl => { :verify => false })
87
- Pling::Gateway::C2DM.new(valid_configuration.merge(:connection => { :ssl => { :verify => false }}))
91
+ Pling::C2DM::Gateway.new(valid_configuration.merge(:connection => { :ssl => { :verify => false }}))
88
92
  end
89
93
 
90
94
  it 'should use Faraday::Response::Logger when :debug is set to true' do
@@ -92,7 +96,7 @@ describe Pling::Gateway::C2DM do
92
96
  builder.should_receive(:use).with(Faraday::Response::Logger)
93
97
  Faraday.stub(:new).and_yield(builder).and_return(connection_mock)
94
98
 
95
- Pling::Gateway::C2DM.new(valid_configuration.merge(:debug => true))
99
+ Pling::C2DM::Gateway.new(valid_configuration.merge(:debug => true))
96
100
  end
97
101
 
98
102
  it 'should use the adapter set with :adapter' do
@@ -100,7 +104,7 @@ describe Pling::Gateway::C2DM do
100
104
  builder.should_receive(:adapter).with(:typheus)
101
105
  Faraday.stub(:new).and_yield(builder).and_return(connection_mock)
102
106
 
103
- Pling::Gateway::C2DM.new(valid_configuration.merge(:adapter => :typheus))
107
+ Pling::C2DM::Gateway.new(valid_configuration.merge(:adapter => :typheus))
104
108
  end
105
109
 
106
110
  it 'should allow configuring the authentication_url' do
@@ -108,13 +112,13 @@ describe Pling::Gateway::C2DM do
108
112
  with('http://example.com/authentication', anything).
109
113
  and_return(authentication_response_mock)
110
114
 
111
- Pling::Gateway::C2DM.new(valid_configuration.merge(:authentication_url => 'http://example.com/authentication'))
115
+ Pling::C2DM::Gateway.new(valid_configuration.merge(:authentication_url => 'http://example.com/authentication'))
112
116
  end
113
117
  end
114
118
  end
115
119
 
116
120
  describe '#deliver' do
117
- subject { Pling::Gateway::C2DM.new(valid_configuration) }
121
+ subject { Pling::C2DM::Gateway.new(valid_configuration) }
118
122
 
119
123
  let(:message) { Pling::Message.new('Hello from Pling') }
120
124
  let(:device) { Pling::Device.new(:identifier => 'DEVICEIDENTIFIER', :type => :android) }
@@ -160,23 +164,78 @@ describe Pling::Gateway::C2DM do
160
164
  end
161
165
 
162
166
  it 'should raise a Pling::DeliveryFailed exception if the delivery was not successful' do
163
- connection_mock.should_receive(:post).
164
- with('https://android.apis.google.com/c2dm/send', valid_push_params, valid_push_headers).
165
- and_return(push_response_mock)
166
-
167
+ connection_mock.should_receive(:post).and_return(push_response_mock)
167
168
  push_response_mock.stub(:status => 401, :success? => false, :body => "Something went wrong")
168
169
 
169
170
  expect { subject.deliver(message, device) }.to raise_error Pling::DeliveryFailed, /Something went wrong/
170
171
  end
171
172
 
172
173
  it 'should raise a Pling::DeliveryFailed exception if the response body contained an error' do
174
+ connection_mock.should_receive(:post).and_return(push_response_mock)
175
+ push_response_mock.stub(:status => 200, :success? => true, :body => "Error=SomeError")
176
+
177
+ expect { subject.deliver(message, device) }.to raise_error Pling::DeliveryFailed, /Error=SomeError/
178
+ end
179
+
180
+ it 'should send data.badge if the given message has a badge' do
173
181
  connection_mock.should_receive(:post).
174
- with('https://android.apis.google.com/c2dm/send', valid_push_params, valid_push_headers).
182
+ with(anything, hash_including(:'data.badge' => '10'), anything).
175
183
  and_return(push_response_mock)
184
+ message.badge = 10
185
+ subject.deliver(message, device)
186
+ end
176
187
 
177
- push_response_mock.stub(:status => 200, :success? => true, :body => "Error=SomeError")
188
+ it 'should send data.sound if the given message has a sound' do
189
+ connection_mock.should_receive(:post).
190
+ with(anything, hash_including(:'data.sound' => 'pling'), anything).
191
+ and_return(push_response_mock)
192
+ message.sound = :pling
193
+ subject.deliver(message, device)
194
+ end
178
195
 
179
- expect { subject.deliver(message, device) }.to raise_error Pling::DeliveryFailed, /Error=SomeError/
196
+ it 'should send data.subject if the given message has a subject' do
197
+ connection_mock.should_receive(:post).
198
+ with(anything, hash_including(:'data.subject' => 'Important!'), anything).
199
+ and_return(push_response_mock)
200
+ message.subject = 'Important!'
201
+ subject.deliver(message, device)
202
+ end
203
+
204
+ context 'when configured to include payload' do
205
+ before do
206
+ valid_configuration.merge!(:payload => true)
207
+ message.payload = { :data => 'available' }
208
+ end
209
+
210
+ it 'should include the given payload' do
211
+ connection_mock.should_receive(:post).
212
+ with(anything, hash_including(:'data.data' => 'available'), anything).
213
+ and_return(push_response_mock)
214
+ subject.deliver(message, device)
215
+ end
216
+ end
217
+
218
+ context 'when configured to not include payload' do
219
+ before do
220
+ valid_configuration.merge!(:payload => false)
221
+ message.payload = { :data => 'available' }
222
+ end
223
+
224
+ it 'should include the given payload' do
225
+ connection_mock.should_receive(:post).
226
+ with(anything, hash_not_including(:'data.data' => 'available'), anything).
227
+ and_return(push_response_mock)
228
+ subject.deliver(message, device)
229
+ end
230
+ end
231
+
232
+ [:QuotaExceeded, :DeviceQuotaExceeded,
233
+ :InvalidRegistration, :NotRegistered,
234
+ :MessageTooBig, :MissingCollapseKey].each do |exception|
235
+ it "should raise a Pling::C2DM::#{exception} when the response body is ''" do
236
+ push_response_mock.stub(:body => "Error=#{exception}")
237
+ expect { subject.deliver(message, device) }.to raise_error Pling::C2DM.const_get(exception)
238
+ end
180
239
  end
181
240
  end
182
- end
241
+ end