grocer 0.0.6 → 0.0.7
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +50 -9
- data/grocer.gemspec +1 -1
- data/lib/grocer.rb +6 -0
- data/lib/grocer/connection.rb +1 -2
- data/lib/grocer/notification_reader.rb +39 -0
- data/lib/grocer/push_connection.rb +9 -1
- data/lib/grocer/server.rb +39 -0
- data/lib/grocer/ssl_connection.rb +6 -3
- data/lib/grocer/ssl_server.rb +59 -0
- data/lib/grocer/test/server.crt +15 -0
- data/lib/grocer/test/server.csr +12 -0
- data/lib/grocer/test/server.key +15 -0
- data/lib/grocer/version.rb +1 -1
- data/spec/grocer/connection_spec.rb +0 -5
- data/spec/grocer/notification_reader_spec.rb +66 -0
- data/spec/grocer/push_connection_spec.rb +2 -2
- data/spec/grocer/server_spec.rb +37 -0
- data/spec/grocer/ssl_server_spec.rb +47 -0
- data/spec/grocer_spec.rb +16 -0
- metadata +27 -16
- data/lib/grocer/no_certificate_error.rb +0 -4
data/README.md
CHANGED
@@ -33,20 +33,21 @@ gem 'grocer'
|
|
33
33
|
# Information on obtaining a `.pem` file for use with `certificate` is shown
|
34
34
|
# later.
|
35
35
|
pusher = Grocer.pusher(
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
36
|
+
certificate: "/path/to/cert.pem", # required
|
37
|
+
passphrase: "", # optional
|
38
|
+
gateway: "gateway.push.apple.com", # optional; See note below.
|
39
|
+
port: 2195, # optional
|
40
|
+
retries: 3 # optional
|
41
41
|
)
|
42
42
|
```
|
43
43
|
|
44
44
|
#### Notes
|
45
45
|
|
46
|
-
* `gateway`: Defaults to
|
47
|
-
|
48
|
-
`
|
49
|
-
|
46
|
+
* `gateway`: Defaults to different values depending on the `RAILS_ENV` or
|
47
|
+
`RACK_ENV` environment variables. If set to `production`, defaults to
|
48
|
+
`gateway.push.apple.com`, if set to `test`, defaults to `localhost` (see
|
49
|
+
"Acceptance Testing" later), otherwise defaults to
|
50
|
+
`gateway.sandbox.push.apple.com`.
|
50
51
|
* `retries`: The number of times **grocer** will retry writing to or reading
|
51
52
|
from the Apple Push Notification Service before raising any errors to client
|
52
53
|
code.
|
@@ -175,3 +176,43 @@ openssl pkcs12 -in exported_certificate.p12 -out certificate.pem -nodes -clcerts
|
|
175
176
|
```
|
176
177
|
|
177
178
|
The `certificate.pem` file that is generated can be used with **grocer**.
|
179
|
+
|
180
|
+
## Acceptance Testing
|
181
|
+
|
182
|
+
** YET TO BE IMPLEMENTED **
|
183
|
+
|
184
|
+
Grocer ships with framework to setup a real looking APNS server. It listens on
|
185
|
+
a real SSL-capable socket bound to localhost.
|
186
|
+
|
187
|
+
You can setup an APNS client to talk to it, then inspect the notifications the
|
188
|
+
server received.
|
189
|
+
|
190
|
+
The server simply exposes a blocking queue where notifications are placed when
|
191
|
+
they are received. It is your responsibility to timeout if a message is not
|
192
|
+
received in a reasonable amount of time.
|
193
|
+
|
194
|
+
For example, in RSpec:
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
require 'timeout'
|
198
|
+
|
199
|
+
describe "apple push notifications" do
|
200
|
+
before do
|
201
|
+
@server = Grocer.server(port: 2195)
|
202
|
+
@server.accept # starts listening in background
|
203
|
+
end
|
204
|
+
|
205
|
+
after do
|
206
|
+
@server.close
|
207
|
+
end
|
208
|
+
|
209
|
+
specify "As a user, I receive notifications on my phone when awesome things happen" do
|
210
|
+
# ... exercise code that would send APNS notifications ...
|
211
|
+
|
212
|
+
Timeout.timeout(3) {
|
213
|
+
notification = @server.notifications.pop # blocking
|
214
|
+
notification.alert.should == "An awesome thing happened"
|
215
|
+
}
|
216
|
+
end
|
217
|
+
end
|
218
|
+
```
|
data/grocer.gemspec
CHANGED
@@ -25,7 +25,7 @@ Gem::Specification.new do |gem|
|
|
25
25
|
gem.require_paths = ["lib"]
|
26
26
|
gem.version = Grocer::VERSION
|
27
27
|
|
28
|
-
gem.add_development_dependency 'rspec', '~> 2.
|
28
|
+
gem.add_development_dependency 'rspec', '~> 2.10.0'
|
29
29
|
gem.add_development_dependency 'pry', '~> 0.9.8'
|
30
30
|
gem.add_development_dependency 'mocha'
|
31
31
|
gem.add_development_dependency 'bourne'
|
data/lib/grocer.rb
CHANGED
@@ -4,6 +4,7 @@ require_relative 'grocer/feedback_connection'
|
|
4
4
|
require_relative 'grocer/push_connection'
|
5
5
|
require_relative 'grocer/pusher'
|
6
6
|
require_relative 'grocer/version'
|
7
|
+
require_relative 'grocer/server'
|
7
8
|
|
8
9
|
module Grocer
|
9
10
|
|
@@ -21,4 +22,9 @@ module Grocer
|
|
21
22
|
Pusher.new(connection)
|
22
23
|
end
|
23
24
|
|
25
|
+
def self.server(options = { })
|
26
|
+
ssl_server = SSLServer.new(options)
|
27
|
+
Server.new(ssl_server)
|
28
|
+
end
|
29
|
+
|
24
30
|
end
|
data/lib/grocer/connection.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'grocer'
|
2
|
-
require 'grocer/no_certificate_error'
|
3
2
|
require 'grocer/no_gateway_error'
|
4
3
|
require 'grocer/no_port_error'
|
5
4
|
require 'grocer/ssl_connection'
|
@@ -9,7 +8,7 @@ module Grocer
|
|
9
8
|
attr_reader :certificate, :passphrase, :gateway, :port, :retries
|
10
9
|
|
11
10
|
def initialize(options = {})
|
12
|
-
@certificate = options.fetch(:certificate) {
|
11
|
+
@certificate = options.fetch(:certificate) { nil }
|
13
12
|
@gateway = options.fetch(:gateway) { fail NoGatewayError }
|
14
13
|
@port = options.fetch(:port) { fail NoPortError }
|
15
14
|
@passphrase = options.fetch(:passphrase) { nil }
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'json'
|
2
|
+
require_relative 'notification'
|
3
|
+
|
4
|
+
module Grocer
|
5
|
+
class NotificationReader
|
6
|
+
include Enumerable
|
7
|
+
|
8
|
+
def initialize(io)
|
9
|
+
@io = io
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
while notification = read_notification
|
14
|
+
yield notification
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def read_notification
|
21
|
+
@io.read(1) # version (not used for now)
|
22
|
+
|
23
|
+
payload = { }
|
24
|
+
payload[:identifier] = @io.read(4).unpack("N").first
|
25
|
+
payload[:expiry] = Time.at(@io.read(4).unpack("N").first)
|
26
|
+
|
27
|
+
@io.read(2) # device token length (always 32, so not used)
|
28
|
+
payload[:device_token] = @io.read(32).unpack("H*").first
|
29
|
+
|
30
|
+
payload_length = @io.read(2).unpack("n").first
|
31
|
+
payload_hash = JSON.parse(@io.read(payload_length), symbolize_names: true)
|
32
|
+
|
33
|
+
payload.merge!(payload_hash.delete(:aps) || { })
|
34
|
+
payload[:custom] = payload_hash
|
35
|
+
|
36
|
+
Grocer::Notification.new(payload)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -5,6 +5,7 @@ module Grocer
|
|
5
5
|
class PushConnection < SimpleDelegator
|
6
6
|
|
7
7
|
PRODUCTION_GATEWAY = 'gateway.push.apple.com'
|
8
|
+
LOCAL_GATEWAY = '127.0.0.1'
|
8
9
|
SANDBOX_GATEWAY = 'gateway.sandbox.push.apple.com'
|
9
10
|
|
10
11
|
def initialize(options)
|
@@ -22,7 +23,14 @@ module Grocer
|
|
22
23
|
end
|
23
24
|
|
24
25
|
def find_default_gateway
|
25
|
-
Grocer.env
|
26
|
+
case Grocer.env
|
27
|
+
when 'production'
|
28
|
+
PRODUCTION_GATEWAY
|
29
|
+
when 'test'
|
30
|
+
LOCAL_GATEWAY
|
31
|
+
else
|
32
|
+
SANDBOX_GATEWAY
|
33
|
+
end
|
26
34
|
end
|
27
35
|
|
28
36
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'thread'
|
2
|
+
require_relative 'notification_reader'
|
3
|
+
require_relative 'ssl_server'
|
4
|
+
|
5
|
+
module Grocer
|
6
|
+
class Server
|
7
|
+
attr_reader :notifications
|
8
|
+
|
9
|
+
def initialize(server)
|
10
|
+
@server = server
|
11
|
+
|
12
|
+
@clients = []
|
13
|
+
@notifications = Queue.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def accept
|
17
|
+
Thread.new {
|
18
|
+
@server.accept { |client|
|
19
|
+
@clients << client
|
20
|
+
|
21
|
+
Thread.new {
|
22
|
+
# Read from client into queue
|
23
|
+
NotificationReader.new(client).each(¬ifications.method(:push))
|
24
|
+
}
|
25
|
+
}
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def close
|
30
|
+
if @server
|
31
|
+
@server.close
|
32
|
+
@server = nil
|
33
|
+
end
|
34
|
+
|
35
|
+
@clients.each(&:close)
|
36
|
+
@clients = []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -20,10 +20,13 @@ module Grocer
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def connect
|
23
|
-
cert_data = File.read(certificate)
|
24
23
|
context = OpenSSL::SSL::SSLContext.new
|
25
|
-
|
26
|
-
|
24
|
+
|
25
|
+
if certificate
|
26
|
+
cert_data = File.read(certificate)
|
27
|
+
context.key = OpenSSL::PKey::RSA.new(cert_data, passphrase)
|
28
|
+
context.cert = OpenSSL::X509::Certificate.new(cert_data)
|
29
|
+
end
|
27
30
|
|
28
31
|
@sock = TCPSocket.new(gateway, port)
|
29
32
|
@ssl = OpenSSL::SSL::SSLSocket.new(@sock, context)
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'socket'
|
3
|
+
require 'thread'
|
4
|
+
|
5
|
+
module Grocer
|
6
|
+
class SSLServer
|
7
|
+
attr_accessor :port
|
8
|
+
|
9
|
+
def initialize(options = {})
|
10
|
+
options = defaults.merge(options)
|
11
|
+
options.each { |k, v| send("#{k}=", v) }
|
12
|
+
end
|
13
|
+
|
14
|
+
def defaults
|
15
|
+
{
|
16
|
+
port: 2195
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def accept
|
21
|
+
while socket = ssl_socket.accept
|
22
|
+
yield socket if block_given?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
if @ssl_socket
|
28
|
+
@ssl_socket.close
|
29
|
+
@ssl_socket = nil
|
30
|
+
@socket = nil
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def ssl_socket
|
37
|
+
@ssl_socket ||= OpenSSL::SSL::SSLServer.new(socket, context)
|
38
|
+
end
|
39
|
+
|
40
|
+
def socket
|
41
|
+
@socket ||= TCPServer.new('127.0.0.1', port)
|
42
|
+
end
|
43
|
+
|
44
|
+
def context
|
45
|
+
@context ||= OpenSSL::SSL::SSLContext.new.tap do |c|
|
46
|
+
c.cert = OpenSSL::X509::Certificate.new(File.read(crt_path))
|
47
|
+
c.key = OpenSSL::PKey::RSA.new(File.read(key_path))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def crt_path
|
52
|
+
File.join(File.dirname(__FILE__), "test", "server.crt")
|
53
|
+
end
|
54
|
+
|
55
|
+
def key_path
|
56
|
+
File.join(File.dirname(__FILE__), "test", "server.key")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
2
|
+
MIICVzCCAcACCQDW8lrgRNBDkDANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJV
|
3
|
+
UzELMAkGA1UECBMCR0ExEDAOBgNVBAcTB0F0bGFudGExGzAZBgNVBAoTEkhpZ2hn
|
4
|
+
cm9vdmUgU3R1ZGlvczElMCMGA1UEAxMcZmFrZS5ncm9jZXIuYXBucy5leGFtcGxl
|
5
|
+
LmNvbTAeFw0xMjA1MDgxNzAxMDNaFw0yMjA1MDYxNzAxMDNaMHAxCzAJBgNVBAYT
|
6
|
+
AlVTMQswCQYDVQQIEwJHQTEQMA4GA1UEBxMHQXRsYW50YTEbMBkGA1UEChMSSGln
|
7
|
+
aGdyb292ZSBTdHVkaW9zMSUwIwYDVQQDExxmYWtlLmdyb2Nlci5hcG5zLmV4YW1w
|
8
|
+
bGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCXB2pln5tJUImFg7T1
|
9
|
+
+qaN7ngtpZUUCC//n96Cs6Kih90XW1RRNHSK+oYB66JzErzgDjF81eApYNzPN89F
|
10
|
+
pvFpd0IWIRM4qnw9fruA3Nub6XUljtCu4n1t4+/1o6VNCl7zcz778qSsDFJYALxn
|
11
|
+
jECl0oUSG46T3kI8Su/Mm6mv6QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAEn783m/
|
12
|
+
BdbRH/PJkGeYPWzUP05hmloo5VT3kbopmRUslqGMVM9vbPpdx7eTGxdPMUfVglK1
|
13
|
+
7vqOwFG6dmzH6gRb2d4+TRafjCK1NBm7fPhKQmxc8WgsqQusMW7SbZ6+OoIMxLbY
|
14
|
+
4s+EqIePtUoY+XqbCKXVdyUI1Kw99oph2UXN
|
15
|
+
-----END CERTIFICATE-----
|
@@ -0,0 +1,12 @@
|
|
1
|
+
-----BEGIN CERTIFICATE REQUEST-----
|
2
|
+
MIIBsDCCARkCAQAwcDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAkdBMRAwDgYDVQQH
|
3
|
+
EwdBdGxhbnRhMRswGQYDVQQKExJIaWdoZ3Jvb3ZlIFN0dWRpb3MxJTAjBgNVBAMT
|
4
|
+
HGZha2UuZ3JvY2VyLmFwbnMuZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD
|
5
|
+
gY0AMIGJAoGBAJcHamWfm0lQiYWDtPX6po3ueC2llRQIL/+f3oKzoqKH3RdbVFE0
|
6
|
+
dIr6hgHronMSvOAOMXzV4Clg3M83z0Wm8Wl3QhYhEziqfD1+u4Dc25vpdSWO0K7i
|
7
|
+
fW3j7/WjpU0KXvNzPvvypKwMUlgAvGeMQKXShRIbjpPeQjxK78ybqa/pAgMBAAGg
|
8
|
+
ADANBgkqhkiG9w0BAQUFAAOBgQBjcLRdwZpWwp7mBrn2Nw8opFtVbB/jlPTSh794
|
9
|
+
vdRuVrYAUs11q4qg1viNvZ0qoa6J//U8MkuzTI4JwpIKKUCenUZDC+es7KjwjiGE
|
10
|
+
93RmiT0fqzdIw/TVaKpbsdqmI5ZeQyKpN/UXImftO5xG0ARmQRxVpKnlsiZPTIuR
|
11
|
+
BroEgQ==
|
12
|
+
-----END CERTIFICATE REQUEST-----
|
@@ -0,0 +1,15 @@
|
|
1
|
+
-----BEGIN RSA PRIVATE KEY-----
|
2
|
+
MIICXAIBAAKBgQCXB2pln5tJUImFg7T1+qaN7ngtpZUUCC//n96Cs6Kih90XW1RR
|
3
|
+
NHSK+oYB66JzErzgDjF81eApYNzPN89FpvFpd0IWIRM4qnw9fruA3Nub6XUljtCu
|
4
|
+
4n1t4+/1o6VNCl7zcz778qSsDFJYALxnjECl0oUSG46T3kI8Su/Mm6mv6QIDAQAB
|
5
|
+
AoGBAIfLRSEmhws+fMgtihH5UrQfDLOORCKE0hN3fSvrtHmKy4Hqvj9deMRVSRSE
|
6
|
+
98WbvXN/j4N9ElZiH2e5+IXZ+wjFddSmksKCJEj8IV0oPikNYwEg1pFo5vMSHMTA
|
7
|
+
4kBcx6tgKH8kjPxGz1w5RkxvwdqkKyFDask2Xxn4DDfYI6ZhAkEAxdPun7Yf3EOz
|
8
|
+
/OrHAEC1WXaQlrojcBheNSJiON+joiPeXSqbKXDAkHu8pQWawilIm7M/fIGADT9c
|
9
|
+
BkWqlbCopQJBAMNwj4bSpNKa83URN8YWJYPocvqb8ZqoS7oZaJLklrRIxnzPACKa
|
10
|
+
GyJlj2z6ADotJTbdMu0hBjjXf/BAX117AvUCQACRRxH2N8kt+Io1MjTx+pMzH98O
|
11
|
+
0aM0rrCAVL/NBG8mozCpOqC3zhWcBUKD7Zm4/JhVv0zgIjnngKAT+xVK2HECQCJp
|
12
|
+
KCwx3GlkdOcwz+QltBdEjzIG0QRNC4BJxvrOGqbFhYUmITz2az6kKRuj7PRRTJMb
|
13
|
+
YUMVJHZPoywW+XOJHB0CQBtW+13o60wQfluoQZs1DeHRTVSV5sEUUTZuLdp88dxA
|
14
|
+
XZPsIY2e3fgrwpgohGvCZ8q7Em9PP9UiWCnEDRhjRW8=
|
15
|
+
-----END RSA PRIVATE KEY-----
|
data/lib/grocer/version.rb
CHANGED
@@ -11,11 +11,6 @@ describe Grocer::Connection do
|
|
11
11
|
Grocer::SSLConnection.stubs(:new).returns(ssl)
|
12
12
|
end
|
13
13
|
|
14
|
-
it 'requires a certificate' do
|
15
|
-
connection_options.delete(:certificate)
|
16
|
-
-> { described_class.new(connection_options) }.should raise_error(Grocer::NoCertificateError)
|
17
|
-
end
|
18
|
-
|
19
14
|
it 'can be initialized with a certificate' do
|
20
15
|
subject.certificate.should == '/path/to/cert.pem'
|
21
16
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'stringio'
|
3
|
+
require 'grocer/notification_reader'
|
4
|
+
|
5
|
+
describe Grocer::NotificationReader do
|
6
|
+
let(:io) { StringIO.new }
|
7
|
+
subject { described_class.new(io) }
|
8
|
+
|
9
|
+
context "Version 1 messages" do
|
10
|
+
it "reads identifier" do
|
11
|
+
io.write(Grocer::Notification.new(identifier: 1234, alert: "Foo").to_bytes)
|
12
|
+
io.rewind
|
13
|
+
|
14
|
+
notification = subject.first
|
15
|
+
notification.identifier.should == 1234
|
16
|
+
end
|
17
|
+
|
18
|
+
it "reads expiry" do
|
19
|
+
io.write(Grocer::Notification.new(expiry: Time.utc(2013, 3, 24), alert: "Foo").to_bytes)
|
20
|
+
io.rewind
|
21
|
+
|
22
|
+
notification = subject.first
|
23
|
+
notification.expiry.should == Time.utc(2013, 3, 24)
|
24
|
+
end
|
25
|
+
|
26
|
+
it "reads device token" do
|
27
|
+
io.write(Grocer::Notification.new(device_token: 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2', alert: "Foo").to_bytes)
|
28
|
+
io.rewind
|
29
|
+
|
30
|
+
notification = subject.first
|
31
|
+
notification.device_token.should == 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
|
32
|
+
end
|
33
|
+
|
34
|
+
it "reads alert" do
|
35
|
+
io.write(Grocer::Notification.new(alert: "Foo").to_bytes)
|
36
|
+
io.rewind
|
37
|
+
|
38
|
+
notification = subject.first
|
39
|
+
notification.alert.should == "Foo"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "reads badge" do
|
43
|
+
io.write(Grocer::Notification.new(alert: "Foo", badge: 5).to_bytes)
|
44
|
+
io.rewind
|
45
|
+
|
46
|
+
notification = subject.first
|
47
|
+
notification.badge.should == 5
|
48
|
+
end
|
49
|
+
|
50
|
+
it "reads sound" do
|
51
|
+
io.write(Grocer::Notification.new(alert: "Foo", sound: "foo.aiff").to_bytes)
|
52
|
+
io.rewind
|
53
|
+
|
54
|
+
notification = subject.first
|
55
|
+
notification.sound.should == "foo.aiff"
|
56
|
+
end
|
57
|
+
|
58
|
+
it "reads custom attributes" do
|
59
|
+
io.write(Grocer::Notification.new(alert: "Foo", custom: { foo: "bar" }).to_bytes)
|
60
|
+
io.rewind
|
61
|
+
|
62
|
+
notification = subject.first
|
63
|
+
notification.custom.should == { foo: "bar" }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -35,9 +35,9 @@ describe Grocer::PushConnection do
|
|
35
35
|
subject.gateway.should == 'gateway.sandbox.push.apple.com'
|
36
36
|
end
|
37
37
|
|
38
|
-
it 'defaults to the
|
38
|
+
it 'defaults to the localhost Apple push gateway in test environment' do
|
39
39
|
Grocer.stubs(:env).returns('test')
|
40
|
-
subject.gateway.should == '
|
40
|
+
subject.gateway.should == '127.0.0.1'
|
41
41
|
end
|
42
42
|
|
43
43
|
it 'defaults to the sandboxed Apple push gateway for other random values' do
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'timeout'
|
3
|
+
require 'stringio'
|
4
|
+
require 'grocer/server'
|
5
|
+
require 'grocer/notification'
|
6
|
+
|
7
|
+
describe Grocer::Server do
|
8
|
+
let(:ssl_server) { stub_everything }
|
9
|
+
let(:mock_client) { StringIO.new }
|
10
|
+
subject { described_class.new(ssl_server) }
|
11
|
+
|
12
|
+
before do
|
13
|
+
ssl_server.stubs(:accept).yields(mock_client)
|
14
|
+
mock_client.stubs(:close)
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
subject.close
|
19
|
+
end
|
20
|
+
|
21
|
+
it "accepts client connections and reads notifications into a queue" do
|
22
|
+
mock_client.write(Grocer::Notification.new(alert: "Hi!").to_bytes)
|
23
|
+
mock_client.rewind
|
24
|
+
|
25
|
+
subject.accept
|
26
|
+
Timeout.timeout(5) {
|
27
|
+
notification = subject.notifications.pop
|
28
|
+
notification.alert.should == "Hi!"
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
it "closes the socket" do
|
33
|
+
ssl_server.expects(:close).at_least(1)
|
34
|
+
|
35
|
+
subject.close
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'grocer/ssl_server'
|
3
|
+
|
4
|
+
describe Grocer::SSLServer do
|
5
|
+
subject { described_class.new(options) }
|
6
|
+
let(:options) { { port: 12345 } }
|
7
|
+
let(:mock_server) { stub_everything }
|
8
|
+
let(:mock_ssl_server) { stub_everything }
|
9
|
+
let(:mock_client) { stub_everything }
|
10
|
+
|
11
|
+
before do
|
12
|
+
TCPServer.stubs(:new).returns(mock_server)
|
13
|
+
OpenSSL::SSL::SSLServer.stubs(:new).returns(mock_ssl_server)
|
14
|
+
|
15
|
+
mock_ssl_server.stubs(:accept).returns(mock_client).then.returns(nil)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "is constructed with a port option" do
|
19
|
+
subject.port.should == 12345
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
describe "#accept" do
|
24
|
+
it "accepts client connections, yielding the client socket" do
|
25
|
+
clients = []
|
26
|
+
subject.accept { |c| clients << c }
|
27
|
+
|
28
|
+
clients.should == [mock_client]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "#close" do
|
33
|
+
it "closes the SSL socket" do
|
34
|
+
mock_ssl_server.expects(:close)
|
35
|
+
|
36
|
+
subject.accept # "open" socket
|
37
|
+
subject.close
|
38
|
+
end
|
39
|
+
|
40
|
+
it "is a no-op if the server has not been started" do
|
41
|
+
mock_server.expects(:close).never
|
42
|
+
mock_ssl_server.expects(:close).never
|
43
|
+
|
44
|
+
subject.close
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/spec/grocer_spec.rb
CHANGED
@@ -59,6 +59,22 @@ describe Grocer do
|
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
|
+
describe '.server' do
|
63
|
+
let(:ssl_server) { stub_everything('SSLServer') }
|
64
|
+
before do
|
65
|
+
Grocer::SSLServer.stubs(:new).returns(ssl_server)
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'gets Server' do
|
69
|
+
subject.server(connection_options).should be_a Grocer::Server
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'passes the connection options on to the underlying SSLServer' do
|
73
|
+
subject.server(connection_options)
|
74
|
+
Grocer::SSLServer.should have_received(:new).with(connection_options)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
62
78
|
end
|
63
79
|
|
64
80
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grocer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,22 +11,22 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-05-11 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rspec
|
18
|
-
requirement: &
|
18
|
+
requirement: &70262264103600 !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ~>
|
22
22
|
- !ruby/object:Gem::Version
|
23
|
-
version: 2.
|
23
|
+
version: 2.10.0
|
24
24
|
type: :development
|
25
25
|
prerelease: false
|
26
|
-
version_requirements: *
|
26
|
+
version_requirements: *70262264103600
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: pry
|
29
|
-
requirement: &
|
29
|
+
requirement: &70262264103100 !ruby/object:Gem::Requirement
|
30
30
|
none: false
|
31
31
|
requirements:
|
32
32
|
- - ~>
|
@@ -34,10 +34,10 @@ dependencies:
|
|
34
34
|
version: 0.9.8
|
35
35
|
type: :development
|
36
36
|
prerelease: false
|
37
|
-
version_requirements: *
|
37
|
+
version_requirements: *70262264103100
|
38
38
|
- !ruby/object:Gem::Dependency
|
39
39
|
name: mocha
|
40
|
-
requirement: &
|
40
|
+
requirement: &70262264102720 !ruby/object:Gem::Requirement
|
41
41
|
none: false
|
42
42
|
requirements:
|
43
43
|
- - ! '>='
|
@@ -45,10 +45,10 @@ dependencies:
|
|
45
45
|
version: '0'
|
46
46
|
type: :development
|
47
47
|
prerelease: false
|
48
|
-
version_requirements: *
|
48
|
+
version_requirements: *70262264102720
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
50
|
name: bourne
|
51
|
-
requirement: &
|
51
|
+
requirement: &70262264102260 !ruby/object:Gem::Requirement
|
52
52
|
none: false
|
53
53
|
requirements:
|
54
54
|
- - ! '>='
|
@@ -56,10 +56,10 @@ dependencies:
|
|
56
56
|
version: '0'
|
57
57
|
type: :development
|
58
58
|
prerelease: false
|
59
|
-
version_requirements: *
|
59
|
+
version_requirements: *70262264102260
|
60
60
|
- !ruby/object:Gem::Dependency
|
61
61
|
name: rake
|
62
|
-
requirement: &
|
62
|
+
requirement: &70262264101840 !ruby/object:Gem::Requirement
|
63
63
|
none: false
|
64
64
|
requirements:
|
65
65
|
- - ! '>='
|
@@ -67,7 +67,7 @@ dependencies:
|
|
67
67
|
version: '0'
|
68
68
|
type: :development
|
69
69
|
prerelease: false
|
70
|
-
version_requirements: *
|
70
|
+
version_requirements: *70262264101840
|
71
71
|
description: ! " Grocer interfaces with the Apple Push\n
|
72
72
|
\ Notification Service to send push\n notifications
|
73
73
|
to iOS devices and collect\n notification feedback via
|
@@ -96,14 +96,19 @@ files:
|
|
96
96
|
- lib/grocer/feedback.rb
|
97
97
|
- lib/grocer/feedback_connection.rb
|
98
98
|
- lib/grocer/invalid_format_error.rb
|
99
|
-
- lib/grocer/no_certificate_error.rb
|
100
99
|
- lib/grocer/no_gateway_error.rb
|
101
100
|
- lib/grocer/no_payload_error.rb
|
102
101
|
- lib/grocer/no_port_error.rb
|
103
102
|
- lib/grocer/notification.rb
|
103
|
+
- lib/grocer/notification_reader.rb
|
104
104
|
- lib/grocer/push_connection.rb
|
105
105
|
- lib/grocer/pusher.rb
|
106
|
+
- lib/grocer/server.rb
|
106
107
|
- lib/grocer/ssl_connection.rb
|
108
|
+
- lib/grocer/ssl_server.rb
|
109
|
+
- lib/grocer/test/server.crt
|
110
|
+
- lib/grocer/test/server.csr
|
111
|
+
- lib/grocer/test/server.key
|
107
112
|
- lib/grocer/version.rb
|
108
113
|
- spec/fixtures/example.cer
|
109
114
|
- spec/fixtures/example.key
|
@@ -112,10 +117,13 @@ files:
|
|
112
117
|
- spec/grocer/failed_delivery_attempt_spec.rb
|
113
118
|
- spec/grocer/feedback_connection_spec.rb
|
114
119
|
- spec/grocer/feedback_spec.rb
|
120
|
+
- spec/grocer/notification_reader_spec.rb
|
115
121
|
- spec/grocer/notification_spec.rb
|
116
122
|
- spec/grocer/push_connection_spec.rb
|
117
123
|
- spec/grocer/pusher_spec.rb
|
124
|
+
- spec/grocer/server_spec.rb
|
118
125
|
- spec/grocer/ssl_connection_spec.rb
|
126
|
+
- spec/grocer/ssl_server_spec.rb
|
119
127
|
- spec/grocer_spec.rb
|
120
128
|
- spec/spec_helper.rb
|
121
129
|
homepage: https://github.com/highgroove/grocer
|
@@ -132,7 +140,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
132
140
|
version: '0'
|
133
141
|
segments:
|
134
142
|
- 0
|
135
|
-
hash: -
|
143
|
+
hash: -259006450942006019
|
136
144
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
137
145
|
none: false
|
138
146
|
requirements:
|
@@ -141,7 +149,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
141
149
|
version: '0'
|
142
150
|
segments:
|
143
151
|
- 0
|
144
|
-
hash: -
|
152
|
+
hash: -259006450942006019
|
145
153
|
requirements: []
|
146
154
|
rubyforge_project:
|
147
155
|
rubygems_version: 1.8.15
|
@@ -156,9 +164,12 @@ test_files:
|
|
156
164
|
- spec/grocer/failed_delivery_attempt_spec.rb
|
157
165
|
- spec/grocer/feedback_connection_spec.rb
|
158
166
|
- spec/grocer/feedback_spec.rb
|
167
|
+
- spec/grocer/notification_reader_spec.rb
|
159
168
|
- spec/grocer/notification_spec.rb
|
160
169
|
- spec/grocer/push_connection_spec.rb
|
161
170
|
- spec/grocer/pusher_spec.rb
|
171
|
+
- spec/grocer/server_spec.rb
|
162
172
|
- spec/grocer/ssl_connection_spec.rb
|
173
|
+
- spec/grocer/ssl_server_spec.rb
|
163
174
|
- spec/grocer_spec.rb
|
164
175
|
- spec/spec_helper.rb
|