grocer 0.0.6 → 0.0.7
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/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
|