pling 0.0.1 → 0.1.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.
@@ -0,0 +1,103 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::Gateway::APN do
4
+
5
+ let(:valid_configuration) { { :certificate => '/path/to/certificate.pem' } }
6
+
7
+ let(:message) { Pling::Message.new('Hello from Pling') }
8
+ let(:device) { Pling::Device.new(:identifier => 'DEVICEIDENTIFIER', :type => :iphone) }
9
+
10
+ let(:ssl_context) { double(OpenSSL::SSL::SSLContext).as_null_object }
11
+ let(:x509_certificate) { double(OpenSSL::X509::Certificate).as_null_object }
12
+ let(:pkey_rsa) { double(OpenSSL::PKey::RSA).as_null_object }
13
+ let(:tcp_socket) { double(TCPSocket).as_null_object }
14
+ let(:ssl_socket) { double(OpenSSL::SSL::SSLSocket).as_null_object }
15
+
16
+ before do
17
+ File.stub(:read).with('/path/to/certificate.pem').and_return('--- CERT CONTENT ---')
18
+ OpenSSL::SSL::SSLContext.stub(:new).and_return(ssl_context)
19
+ OpenSSL::X509::Certificate.stub(:new).and_return(x509_certificate)
20
+ OpenSSL::PKey::RSA.stub(:new).and_return(pkey_rsa)
21
+ TCPSocket.stub(:new).and_return(tcp_socket)
22
+ OpenSSL::SSL::SSLSocket.stub(:new).and_return(ssl_socket)
23
+ end
24
+
25
+ it 'should handle various apn related device types' do
26
+ Pling::Gateway::APN.handled_types.should =~ [:apple, :apn, :ios, :ipad, :iphone, :ipod]
27
+ end
28
+
29
+ context 'when created with an invalid configuration' do
30
+ it "should raise an error when :certificate is missing" do
31
+ expect { Pling::Gateway::APN.new({}) }.to raise_error(ArgumentError, /:certificate is missing/)
32
+ end
33
+ end
34
+
35
+ context 'when created with a valid configuration' do
36
+ it 'should allow configuration of the :certificate' do
37
+ File.should_receive(:read).with('/some/path').and_return('--- SOME CERT CONTENT ---')
38
+ gateway = Pling::Gateway::APN.new(valid_configuration.merge(:certificate => '/some/path'))
39
+ gateway.deliver(message, device)
40
+ end
41
+
42
+ it 'should allow configuration of the :host' do
43
+ TCPSocket.should_receive(:new).with('some-host', anything).and_return(tcp_socket)
44
+ gateway = Pling::Gateway::APN.new(valid_configuration.merge(:host => 'some-host'))
45
+ gateway.deliver(message, device)
46
+ end
47
+
48
+ it 'should allow configuration of the :port' do
49
+ TCPSocket.should_receive(:new).with(anything, 1234).and_return(tcp_socket)
50
+ gateway = Pling::Gateway::APN.new(valid_configuration.merge(:port => 1234))
51
+ gateway.deliver(message, device)
52
+ end
53
+
54
+ it 'should establish a connection' do
55
+ ssl_socket.should_receive(:connect)
56
+ Pling::Gateway::APN.new(valid_configuration)
57
+ end
58
+ end
59
+
60
+ describe '#deliver' do
61
+ subject { Pling::Gateway::APN.new(valid_configuration) }
62
+
63
+ it 'should raise an error if no message is given' do
64
+ expect { subject.deliver(nil, device) }.to raise_error
65
+ end
66
+
67
+ it 'should raise an error the device is given' do
68
+ expect { subject.deliver(message, nil) }.to raise_error
69
+ end
70
+
71
+ it 'should call #to_pling_message on the given message' do
72
+ message.should_receive(:to_pling_message).and_return(message)
73
+ subject.deliver(message, device)
74
+ end
75
+
76
+ it 'should call #to_pling_device on the given device' do
77
+ device.should_receive(:to_pling_device).and_return(device)
78
+ subject.deliver(message, device)
79
+ end
80
+
81
+ it 'should raise an exception when the payload exceeds 256 bytes' do
82
+ message.body = "X" * 256
83
+ expect { subject.deliver(message, device) }.to raise_error(Pling::DeliveryFailed, /Payload size of \d+ exceeds allowed size of 256 bytes/)
84
+ end
85
+
86
+ it 'should try to deliver the given message' do
87
+ expected_header = "\x00\x00 \xDE\xF2\xCE-\xE7\xD2\xF2\xEB\x00"
88
+ expected_payload = {
89
+ 'aps' => {
90
+ 'alert' => 'Hello from Pling'
91
+ }
92
+ }
93
+
94
+ ssl_socket.stub(:write) do |packet|
95
+ header, payload = packet.split('$')
96
+ header.should eq(expected_header)
97
+ JSON.parse(payload).should eq(expected_payload)
98
+ end
99
+
100
+ subject.deliver(message, device)
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::Gateway::Base do
4
+
5
+ let(:gateway_class) do
6
+ Class.new(Pling::Gateway::Base).tap do |klass|
7
+ klass.handles :android, :c2dm
8
+ end
9
+ end
10
+
11
+ let(:gateway) { gateway_class.new }
12
+ let(:message) { Pling::Message.new }
13
+ let(:device) { Pling::Device.new }
14
+
15
+ describe '#handles?' do
16
+ it 'should return true if the gateway supports the given device\'s type' do
17
+ device.type = :android
18
+ gateway.handles?(device).should be_true
19
+
20
+ device.type = :c2dm
21
+ gateway.handles?(device).should be_true
22
+ end
23
+
24
+ it 'should return false if the gateway does not support the given device\'s type' do
25
+ device.type = :random
26
+ gateway.handles?(device).should be_false
27
+ end
28
+ end
29
+
30
+ describe '#deliver' do
31
+
32
+ it 'should call each middleware in the given order' do
33
+ first_middleware = double(Pling::Middleware::Base)
34
+ first_middleware.should_receive(:deliver).
35
+ with(message, device).and_yield(message, device)
36
+
37
+ second_middleware = double(Pling::Middleware::Base)
38
+ second_middleware.should_receive(:deliver).
39
+ with(message, device)
40
+
41
+ gateway = gateway_class.new(:middlewares => [first_middleware, second_middleware])
42
+ gateway.stub(:deliver!)
43
+
44
+ gateway.deliver(message, device)
45
+ end
46
+
47
+ it 'should raise an error if #deliver! is not overwritten' do
48
+ expect { gateway.deliver(message, device) }.to raise_error(/Please implement/)
49
+ end
50
+
51
+ it 'should not modify the middleware configuration' do
52
+ middlewares = [Pling::Middleware::Base.new, Pling::Middleware::Base.new]
53
+
54
+ gateway = gateway_class.new(:middlewares => middlewares)
55
+ gateway.stub(:deliver!)
56
+
57
+ expect { gateway.deliver(message, device) }.to_not change(middlewares, :count)
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,182 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::Gateway::C2DM do
4
+
5
+ let(:valid_configuration) do
6
+ { :email => 'someone@gmail.com', :password => 'random', :source => 'some-source' }
7
+ end
8
+
9
+ let(:authentication_response_mock) do
10
+ mock('Faraday authentication response', :success? => true, :status => 200, :body => "SID=SOMESID\nAuth=S0ME-ToKeN123")
11
+ end
12
+
13
+ let(:push_response_mock) do
14
+ mock('Faraday send response', :success? => true, :status => 200, :body => "")
15
+ end
16
+
17
+ let(:connection_mock) do
18
+ mock('Faraday connection').tap do |mock|
19
+ mock.stub(:post).
20
+ with('https://www.google.com/accounts/ClientLogin', anything).
21
+ and_return(authentication_response_mock)
22
+
23
+ mock.stub(:post).
24
+ with('https://android.apis.google.com/c2dm/send', anything, anything).
25
+ and_return(push_response_mock)
26
+ end
27
+ end
28
+
29
+ before { Faraday.stub(:new).and_return(connection_mock) }
30
+
31
+ context 'when created with an invalid configuration' do
32
+ [:email, :password, :source].each do |attribute|
33
+ it "should raise an error when :#{attribute} is missing" do
34
+ configuration = valid_configuration
35
+ configuration.delete(attribute)
36
+ expect { Pling::Gateway::C2DM.new(configuration) }.to raise_error(ArgumentError, /:#{attribute} is missing/)
37
+ end
38
+ end
39
+ end
40
+
41
+ context 'when created with valid configuration' do
42
+
43
+ context 'authenticating' do
44
+ let(:valid_authentication_params) do
45
+ {
46
+ :accountType => 'HOSTED_OR_GOOGLE',
47
+ :service => 'ac2dm',
48
+ :Email => valid_configuration[:email],
49
+ :Passwd => valid_configuration[:password],
50
+ :source => valid_configuration[:source]
51
+ }
52
+ end
53
+
54
+ it 'should not raise an error' do
55
+ expect { Pling::Gateway::C2DM.new(valid_configuration) }.to_not raise_error
56
+ end
57
+
58
+ it 'should try to authenticate' do
59
+ connection_mock.should_receive(:post).
60
+ with('https://www.google.com/accounts/ClientLogin', valid_authentication_params).
61
+ and_return(authentication_response_mock)
62
+
63
+ Pling::Gateway::C2DM.new(valid_configuration)
64
+ end
65
+
66
+ it 'should extract the token from the response body' do
67
+ gateway = Pling::Gateway::C2DM.new(valid_configuration)
68
+ gateway.token.should eq('S0ME-ToKeN123')
69
+ end
70
+
71
+ it 'should raise an error if authentication was not successful' do
72
+ authentication_response_mock.stub(:status => 403, :success? => false, :body => 'Error=BadAuthentication')
73
+
74
+ expect { Pling::Gateway::C2DM.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Authentication failed: \[403\] Error=BadAuthentication/)
75
+ end
76
+
77
+ it 'should raise an error if it could not extract a token from the response' do
78
+ authentication_response_mock.stub(:body).and_return('SOMERANDOMBODY')
79
+
80
+ expect { Pling::Gateway::C2DM.new(valid_configuration) }.to raise_error(Pling::AuthenticationFailed, /Token extraction failed/)
81
+ end
82
+ end
83
+
84
+ context 'configuration' do
85
+ it 'should allow configuring Faraday\'s :connection settings' do
86
+ Faraday.should_receive(:new).with(:ssl => { :verify => false })
87
+ Pling::Gateway::C2DM.new(valid_configuration.merge(:connection => { :ssl => { :verify => false }}))
88
+ end
89
+
90
+ it 'should use Faraday::Response::Logger when :debug is set to true' do
91
+ builder = mock(:use => true, :adapter => true)
92
+ builder.should_receive(:use).with(Faraday::Response::Logger)
93
+ Faraday.stub(:new).and_yield(builder).and_return(connection_mock)
94
+
95
+ Pling::Gateway::C2DM.new(valid_configuration.merge(:debug => true))
96
+ end
97
+
98
+ it 'should use the adapter set with :adapter' do
99
+ builder = mock(:use => true, :adapter => true)
100
+ builder.should_receive(:adapter).with(:typheus)
101
+ Faraday.stub(:new).and_yield(builder).and_return(connection_mock)
102
+
103
+ Pling::Gateway::C2DM.new(valid_configuration.merge(:adapter => :typheus))
104
+ end
105
+
106
+ it 'should allow configuring the authentication_url' do
107
+ connection_mock.should_receive(:post).
108
+ with('http://example.com/authentication', anything).
109
+ and_return(authentication_response_mock)
110
+
111
+ Pling::Gateway::C2DM.new(valid_configuration.merge(:authentication_url => 'http://example.com/authentication'))
112
+ end
113
+ end
114
+ end
115
+
116
+ describe '#deliver' do
117
+ subject { Pling::Gateway::C2DM.new(valid_configuration) }
118
+
119
+ let(:message) { Pling::Message.new('Hello from Pling') }
120
+ let(:device) { Pling::Device.new(:identifier => 'DEVICEIDENTIFIER', :type => :android) }
121
+
122
+ let(:valid_push_params) do
123
+ {
124
+ :registration_id => device.identifier,
125
+ :'data.body' => message.body,
126
+ :collapse_key => message.body.hash
127
+ }
128
+ end
129
+
130
+ let(:valid_push_headers) do
131
+ {
132
+ :Authorization => 'GoogleLogin auth=S0ME-ToKeN123'
133
+ }
134
+ end
135
+
136
+ it 'should raise an error if no message is given' do
137
+ expect { subject.deliver(nil, device) }.to raise_error
138
+ end
139
+
140
+ it 'should raise an error the device is given' do
141
+ expect { subject.deliver(message, nil) }.to raise_error
142
+ end
143
+
144
+ it 'should call #to_pling_message on the given message' do
145
+ message.should_receive(:to_pling_message).and_return(message)
146
+ subject.deliver(message, device)
147
+ end
148
+
149
+ it 'should call #to_pling_device on the given device' do
150
+ device.should_receive(:to_pling_device).and_return(device)
151
+ subject.deliver(message, device)
152
+ end
153
+
154
+ it 'should try to deliver the given message' do
155
+ connection_mock.should_receive(:post).
156
+ with('https://android.apis.google.com/c2dm/send', valid_push_params, valid_push_headers).
157
+ and_return(push_response_mock)
158
+
159
+ subject.deliver(message, device)
160
+ end
161
+
162
+ 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
+ push_response_mock.stub(:status => 401, :success? => false, :body => "Something went wrong")
168
+
169
+ expect { subject.deliver(message, device) }.to raise_error Pling::DeliveryFailed, /Something went wrong/
170
+ end
171
+
172
+ it 'should raise a Pling::DeliveryFailed exception if the response body contained an error' do
173
+ connection_mock.should_receive(:post).
174
+ with('https://android.apis.google.com/c2dm/send', valid_push_params, valid_push_headers).
175
+ and_return(push_response_mock)
176
+
177
+ push_response_mock.stub(:status => 200, :success? => true, :body => "Error=SomeError")
178
+
179
+ expect { subject.deliver(message, device) }.to raise_error Pling::DeliveryFailed, /Error=SomeError/
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,50 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::Gateway do
4
+
5
+ subject { Pling::Gateway }
6
+
7
+ let(:message) { Pling::Message.new('Hello from Pling') }
8
+ let(:device) { Pling::Device.new(:identifier => 'DEVICEIDENTIFIER', :type => :android) }
9
+
10
+ let(:gateway_class) do
11
+ Class.new(Pling::Gateway::Base).tap do |klass|
12
+ klass.instance_eval do
13
+ handles :android
14
+ end
15
+ end
16
+ end
17
+
18
+ let(:gateway) { gateway_class.new }
19
+
20
+ before { Pling.stub(:gateways).and_return(Pling::DelayedInitializer.new([gateway])) }
21
+
22
+ it { should respond_to(:discover) }
23
+
24
+ describe '.discover' do
25
+ it 'should do a delayed initialization' do
26
+ Pling.stub(:gateways).and_return(Pling::DelayedInitializer.new([[gateway_class, { :some => :option }]]))
27
+ gateway_class.should_receive(:new).with({ :some => :option }).and_return(mock.as_null_object)
28
+ subject.discover(device)
29
+ end
30
+
31
+ it 'should require an argument' do
32
+ expect { subject.discover }.to raise_error ArgumentError
33
+ end
34
+
35
+ it 'should call #to_pling_device on the given argument' do
36
+ device.should_receive(:to_pling_device).and_return(device)
37
+ subject.discover(device)
38
+ end
39
+
40
+ it 'should return a gateway that can handle the given device' do
41
+ subject.discover(device).should be == gateway
42
+ end
43
+
44
+ it 'should raise an Pling::NoGatewayFound error if no gateway was found' do
45
+ device.type = :random
46
+ expect { subject.discover(device) }.to raise_error Pling::NoGatewayFound, /Could not find a gateway for Pling::Device with type :random/
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,44 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling::Message do
4
+
5
+ context 'when created with no arguments' do
6
+ it 'should not require an argument' do
7
+ expect { Pling::Message.new() }.to_not raise_error ArgumentError
8
+ end
9
+
10
+ specify { Pling::Message.new().should_not be_valid }
11
+ end
12
+
13
+ context 'when created with a string as first argument' do
14
+ subject { Pling::Message.new('Hello from Pling') }
15
+ its(:body) { should eq('Hello from Pling') }
16
+ it { should be_valid }
17
+ end
18
+
19
+ context 'when created with a hash that contains a :body key as first argument' do
20
+ subject { Pling::Message.new(:body => 'Hello from Pling') }
21
+ its(:body) { should eq('Hello from Pling') }
22
+ it { should be_valid }
23
+ end
24
+
25
+ context 'when created with an hash of invalid attributes' do
26
+ it 'should ignore the invalid paramters' do
27
+ expect { Pling::Message.new({ :random_param => true }) }.to_not raise_error
28
+ end
29
+ end
30
+
31
+ describe '#body=' do
32
+ it 'should call #to_s on the given body' do
33
+ subject.body = stub(:to_s => 'Hello from Pling')
34
+ subject.body.should eq('Hello from Pling')
35
+ end
36
+ end
37
+
38
+ describe '#to_pling_message' do
39
+ it 'should return self' do
40
+ subject.to_pling_message.should be === subject
41
+ end
42
+ end
43
+
44
+ end
@@ -0,0 +1,117 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pling do
4
+
5
+ subject { Pling }
6
+
7
+ it { should respond_to(:configure) }
8
+
9
+ describe '.configure' do
10
+ it 'should require a block' do
11
+ expect { subject.configure }.to raise_error(ArgumentError, /no block given/i)
12
+ end
13
+
14
+ it 'should call the block' do
15
+ expect { Pling.configure { throw :executed } }.to throw_symbol(:executed)
16
+ end
17
+
18
+ it 'should pass Pling to the block' do
19
+ Pling.configure do |config|
20
+ config.should be(Pling)
21
+ end
22
+ end
23
+ end
24
+
25
+ it { should respond_to(:gateways) }
26
+ it { should respond_to(:gateways=) }
27
+ its(:gateways) { should eq([]) }
28
+ its(:gateways) { should be_kind_of Pling::DelayedInitializer }
29
+
30
+ describe '.gateways=' do
31
+ it 'should not change its type when set to an other array' do
32
+ subject.gateways = []
33
+ subject.gateways.should be_kind_of Pling::DelayedInitializer
34
+ end
35
+ end
36
+
37
+ it { should respond_to(:middlewares) }
38
+ it { should respond_to(:middlewares=) }
39
+ its(:middlewares) { should eq([]) }
40
+ its(:middlewares) { should be_kind_of Pling::DelayedInitializer }
41
+
42
+ describe '.middlewares=' do
43
+ it 'should not change its type when set to an other array' do
44
+ subject.middlewares = []
45
+ subject.middlewares.should be_kind_of Pling::DelayedInitializer
46
+ end
47
+ end
48
+
49
+ it { should respond_to(:adapter) }
50
+ it { should respond_to(:adapter=) }
51
+
52
+ describe '.adapter' do
53
+ it 'should default to Pling::Adapter::Base' do
54
+ subject.adapter.class.should eq(Pling::Adapter::Base)
55
+ end
56
+ end
57
+
58
+ describe '.deliver' do
59
+
60
+ let(:message) { Pling::Message.new }
61
+ let(:device) { Pling::Device.new }
62
+ let(:adapter) { mock(:deliver => true) }
63
+
64
+ before do
65
+ Pling.stub(:adapter).and_return(adapter)
66
+ end
67
+
68
+ it 'should raise an error if no message is given' do
69
+ expect { Pling.deliver(nil, device) }.to raise_error
70
+ end
71
+
72
+ it 'should raise an error the device is given' do
73
+ expect { Pling.deliver(message, nil) }.to raise_error
74
+ end
75
+
76
+ it 'should call #to_pling_message on the given message' do
77
+ message.should_receive(:to_pling_message).and_return(message)
78
+ Pling.deliver(message, device)
79
+ end
80
+
81
+ it 'should call #to_pling_device on the given device' do
82
+ device.should_receive(:to_pling_device).and_return(device)
83
+ Pling.deliver(message, device)
84
+ end
85
+
86
+ it 'should call the adapter' do
87
+ adapter.should_receive(:deliver).with(message, device)
88
+ Pling.deliver(message, device)
89
+ end
90
+
91
+ it 'should call each middleware in the given order' do
92
+ first_middleware = double(Pling::Middleware::Base)
93
+ first_middleware.should_receive(:deliver).
94
+ with(message, device).and_yield(message, device)
95
+
96
+ second_middleware = double(Pling::Middleware::Base)
97
+ second_middleware.should_receive(:deliver).
98
+ with(message, device)
99
+
100
+ Pling.stub(:middlewares).and_return(Pling::DelayedInitializer.new([first_middleware, second_middleware]))
101
+
102
+ Pling.deliver(message, device)
103
+ end
104
+ end
105
+ end
106
+
107
+ describe Pling::AuthenticationFailed do
108
+ it { should be_kind_of Pling::Error }
109
+ end
110
+
111
+ describe Pling::DeliveryFailed do
112
+ it { should be_kind_of Pling::Error }
113
+ end
114
+
115
+ describe Pling::NoGatewayFound do
116
+ it { should be_kind_of Pling::Error }
117
+ end