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/Gemfile +1 -0
- data/Guardfile +4 -0
- data/README.md +8 -7
- data/lib/pling/adapter/base.rb +2 -0
- data/lib/pling/apn/connection.rb +122 -0
- data/lib/pling/apn/feedback.rb +88 -0
- data/lib/pling/{gateway/apn.rb → apn/gateway.rb} +38 -31
- data/lib/pling/apn.rb +13 -0
- data/lib/pling/{gateway/c2dm.rb → c2dm/gateway.rb} +27 -9
- data/lib/pling/c2dm.rb +17 -0
- data/lib/pling/delayed_initializer.rb +3 -1
- data/lib/pling/gateway.rb +120 -4
- data/lib/pling/message.rb +52 -0
- data/lib/pling/middleware/base.rb +31 -0
- data/lib/pling/version.rb +1 -1
- data/lib/pling.rb +24 -2
- data/pling.gemspec +1 -1
- data/spec/pling/.DS_Store +0 -0
- data/spec/pling/adapter/.DS_Store +0 -0
- data/spec/pling/apn/connection_spec.rb +124 -0
- data/spec/pling/apn/feedback_spec.rb +44 -0
- data/spec/pling/apn/gateway_spec.rb +141 -0
- data/spec/pling/{gateway/c2dm_spec.rb → c2dm/gateway_spec.rb} +79 -20
- data/spec/pling/gateway_spec.rb +65 -3
- data/spec/pling/message_spec.rb +25 -0
- data/spec/pling_spec.rb +9 -0
- metadata +31 -22
- data/lib/pling/gateway/base.rb +0 -64
- data/spec/pling/gateway/apn_spec.rb +0 -103
- data/spec/pling/gateway/base_spec.rb +0 -61
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
data/lib/pling.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
|
-
require
|
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 = "
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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('
|
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
|
-
|
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
|
-
|
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
|