pling 0.0.1 → 0.1.0

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