pling 0.1.0 → 0.2.0
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/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
|