grocer 0.2.0 → 0.3.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/.travis.yml CHANGED
@@ -2,7 +2,6 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.2
4
4
  - 1.9.3
5
- - jruby-19mode
6
- - jruby-head # To get JRuby 1.7.x, which defaults to -19mode
5
+ - jruby-19mode # JRuby 1.7.0
7
6
  - rbx-19mode
8
7
  # - ruby-head # seems unstable on travis at this time
data/CHANGELOG.md CHANGED
@@ -1,20 +1,30 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 0.3.0
4
+
5
+ * Add `Grocer::PassbookNotification` for sending, well... Passbook
6
+ notifications. This kind of notification requires no payload.
7
+ * Determining current environment is case-insensitive ([Oriol
8
+ Gual](https://github.com/oriolgual))
9
+
3
10
  ## 0.2.0
4
11
 
5
- * Don't retry connection when the certificate has expired. (Kyle Drake and
6
- Jesse Storimer)
12
+ * Don't retry connection when the certificate has expired. ([Kyle
13
+ Drake](https://github.com/kyledrake) and [Jesse
14
+ Storimer](https://github.com/jstorimer))
7
15
 
8
16
  ## 0.1.1
9
17
 
10
- * Warn that `jruby-openssl` is needed on JRuby platform. (Kyle Drake)
18
+ * Warn that `jruby-openssl` is needed on JRuby platform. ([Kyle
19
+ Drake](https://github.com/kyledrake))
11
20
 
12
21
  ## 0.1.0
13
22
 
14
- * Enables socket keepalive option on APNS client sockets (Kyle Drake)
15
- * Supports non-ASCII characters in notifications (Patrick Van Stee, Andy
16
- Lindeman)
17
- * Certificate can be any object that responds to #read (Kyle Drake)
23
+ * Supports non-ASCII characters in notifications
24
+ * Enables socket keepalive option on APNS client sockets ([Kyle
25
+ Drake](https://github.com/kyledrake))
26
+ * Certificate can be any object that responds to #read ([Kyle
27
+ Drake](https://github.com/kyledrake))
18
28
 
19
29
  ## 0.0.13
20
30
 
data/Gemfile CHANGED
@@ -2,7 +2,3 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in grocer.gemspec
4
4
  gemspec
5
-
6
- platforms :jruby do
7
- gem 'jruby-openssl'
8
- end
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Grocer
2
2
 
3
- [![Build Status](https://secure.travis-ci.org/highgroove/grocer.png)](http://travis-ci.org/highgroove/grocer)
3
+ [![Build Status](https://api.travis-ci.org/highgroove/grocer.png?branch=master)](https://travis-ci.org/highgroove/grocer)
4
4
  [![Dependency Status](https://gemnasium.com/highgroove/grocer.png)](https://gemnasium.com/highgroove/grocer)
5
5
  [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/highgroove/grocer)
6
6
 
@@ -13,7 +13,7 @@ cleanest, most extensible, and friendliest.
13
13
 
14
14
  ## Requirements
15
15
 
16
- * Ruby/MRI 1.9.x or JRuby 1.7.x in 1.9 mode
16
+ * Ruby/MRI 1.9.x, JRuby 1.7.x in 1.9 mode, Rubinius in 1.9 mode
17
17
 
18
18
  ## Installation
19
19
 
@@ -50,7 +50,7 @@ pusher = Grocer.pusher(
50
50
 
51
51
  #### Notes
52
52
 
53
- * `certificate`: If you don't have the certificate stored in a file, you
53
+ * `certificate`: If you don't have the certificate stored in a file, you
54
54
  can pass any object that responds to `read`.
55
55
  Example: `certificate: StringIO.new(pem_string)`
56
56
  * `gateway`: Defaults to different values depending on the `RAILS_ENV` or
@@ -109,6 +109,20 @@ notification = Grocer::Notification.new(
109
109
  # {"aps": {"alert": "Hello from Grocer"}, "acme2": ["bang", "whiz"]}
110
110
  ```
111
111
 
112
+ #### Passbook Notifications
113
+
114
+ A `Grocer::PassbookNotification` is a specialized kind of notification which
115
+ does not require any payload. That is, you need not (and *[Apple explicitly says
116
+ not to](http://developer.apple.com/library/ios/#Documentation/UserExperience/Conceptual/PassKit_PG/Chapters/Updating.html#//apple_ref/doc/uid/TP40012195-CH5-SW1)*)
117
+ send any payload for a Passbook notification. If you do, it will be ignored.
118
+
119
+ ```ruby
120
+ notification = Grocer::PassbookNotification.new(device_token: "...")
121
+
122
+ # Generates a JSON payload like:
123
+ # {"aps": {}}
124
+ ```
125
+
112
126
  ### Feedback
113
127
 
114
128
  ```ruby
@@ -169,7 +183,7 @@ describe "apple push notifications" do
169
183
 
170
184
  Timeout.timeout(3) {
171
185
  notification = @server.notifications.pop # blocking
172
- notification.alert.should == "An awesome thing happened"
186
+ expect(notification.alert).to eq("An awesome thing happened")
173
187
  }
174
188
  end
175
189
  end
data/grocer.gemspec CHANGED
@@ -26,8 +26,6 @@ Gem::Specification.new do |gem|
26
26
  gem.require_paths = ["lib"]
27
27
  gem.version = Grocer::VERSION
28
28
 
29
- gem.add_dependency 'json'
30
-
31
29
  gem.add_development_dependency 'rspec', '~> 2.11'
32
30
  gem.add_development_dependency 'pry', '~> 0.9.8'
33
31
  gem.add_development_dependency 'mocha'
@@ -2,10 +2,20 @@ require 'json'
2
2
  require 'grocer/no_payload_error'
3
3
 
4
4
  module Grocer
5
+ # Public: An object used to send notifications to APNS.
5
6
  class Notification
6
7
  attr_accessor :identifier, :expiry, :device_token, :alert, :badge, :sound,
7
8
  :custom
8
9
 
10
+ # Public: Initialize a new Grocer::Notification. You must specify at least an `alert` or `badge`.
11
+ #
12
+ # payload - The Hash of notification parameters and payload to be sent to APNS.:
13
+ # :device_token - The String representing to device token sent to APNS.
14
+ # :alert - The String or Hash to be sent as the alert portion of the payload. (optional)
15
+ # :badge - The Integer to be sent as the badge portion of the payload. (optional)
16
+ # :sound - The String representing the sound portion of the payload. (optional)
17
+ # :expiry - The Integer representing UNIX epoch date sent to APNS as the notification expiry. (default: 0)
18
+ # :identifier - The arbitrary value sent to APNS to uniquely this notification. (default: 0)
9
19
  def initialize(payload = {})
10
20
  @identifier = 0
11
21
 
@@ -0,0 +1,20 @@
1
+ require 'grocer/notification'
2
+
3
+ module Grocer
4
+ # Public: A specialized form of a Grocer::Notification which requires neither
5
+ # an alert nor badge to be present in the payload. It requires only the
6
+ # `device_token`, and allows an optional `expiry` and `identifier` to be set.
7
+ #
8
+ # Examples
9
+ #
10
+ # Grocer::PassbookNotification.new(device_token: '...')
11
+ class PassbookNotification < Notification
12
+
13
+ private
14
+
15
+ def validate_payload
16
+ true
17
+ end
18
+
19
+ end
20
+ end
@@ -24,7 +24,7 @@ module Grocer
24
24
  end
25
25
 
26
26
  def find_default_gateway
27
- case Grocer.env
27
+ case Grocer.env.downcase
28
28
  when 'production'
29
29
  PRODUCTION_GATEWAY
30
30
  when 'test'
@@ -1,3 +1,3 @@
1
1
  module Grocer
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
@@ -12,25 +12,25 @@ describe Grocer::Connection do
12
12
  end
13
13
 
14
14
  it 'can be initialized with a certificate' do
15
- subject.certificate.should == '/path/to/cert.pem'
15
+ expect(subject.certificate).to eq('/path/to/cert.pem')
16
16
  end
17
17
 
18
18
  it 'defaults to an empty passphrase' do
19
- subject.passphrase.should be_nil
19
+ expect(subject.passphrase).to be_nil
20
20
  end
21
21
 
22
22
  it 'can be initialized with a passphrase' do
23
23
  connection_options[:passphrase] = 'new england clam chowder'
24
- subject.passphrase.should == 'new england clam chowder'
24
+ expect(subject.passphrase).to eq('new england clam chowder')
25
25
  end
26
26
 
27
27
  it 'defaults to 3 retries' do
28
- subject.retries.should == 3
28
+ expect(subject.retries).to eq(3)
29
29
  end
30
30
 
31
31
  it 'can be initialized with a number of retries' do
32
32
  connection_options[:retries] = 2
33
- subject.retries.should == 2
33
+ expect(subject.retries).to eq(2)
34
34
  end
35
35
 
36
36
  it 'requires a gateway' do
@@ -39,7 +39,7 @@ describe Grocer::Connection do
39
39
  end
40
40
 
41
41
  it 'can be initialized with a gateway' do
42
- subject.gateway.should == 'push.example.com'
42
+ expect(subject.gateway).to eq('push.example.com')
43
43
  end
44
44
 
45
45
  it 'requires a port' do
@@ -48,14 +48,14 @@ describe Grocer::Connection do
48
48
  end
49
49
 
50
50
  it 'can be initialized with a port' do
51
- subject.port.should == 443
51
+ expect(subject.port).to eq(443)
52
52
  end
53
53
 
54
54
  it 'can open the connection to the apple push notification service' do
55
55
  subject.connect
56
56
  ssl.should have_received(:connect)
57
57
  end
58
-
58
+
59
59
  it 'raises CertificateExpiredError for OpenSSL::SSL::SSLError with /certificate expired/i message' do
60
60
  ssl.stubs(:write).raises(OpenSSL::SSL::SSLError.new('certificate expired'))
61
61
  -> {subject.write('abc123')}.should raise_error(Grocer::CertificateExpiredError)
@@ -15,22 +15,22 @@ describe Grocer::Extensions::DeepSymbolizeKeys do
15
15
  end
16
16
 
17
17
  it 'does not change nested symbols' do
18
- nested_symbols.deep_symbolize_keys.should == nested_symbols
18
+ expect(nested_symbols.deep_symbolize_keys).to eq(nested_symbols)
19
19
  end
20
20
 
21
21
  it 'symbolizes nested strings' do
22
- nested_strings.deep_symbolize_keys.should == nested_symbols
22
+ expect(nested_strings.deep_symbolize_keys).to eq(nested_symbols)
23
23
  end
24
24
 
25
25
  it 'symbolizes a mix of nested strings and symbols' do
26
- nested_mixed.deep_symbolize_keys.should == nested_symbols
26
+ expect(nested_mixed.deep_symbolize_keys).to eq(nested_symbols)
27
27
  end
28
28
 
29
29
  it 'preserves fixnum keys' do
30
- nested_fixnums.deep_symbolize_keys.should == nested_fixnums
30
+ expect(nested_fixnums.deep_symbolize_keys).to eq(nested_fixnums)
31
31
  end
32
32
 
33
33
  it 'preserves keys that cannot be symbolized' do
34
- nested_illegal_symbols.deep_symbolize_keys.should == nested_illegal_symbols
34
+ expect(nested_illegal_symbols.deep_symbolize_keys).to eq(nested_illegal_symbols)
35
35
  end
36
36
  end
@@ -10,8 +10,8 @@ describe Grocer::FailedDeliveryAttempt do
10
10
  describe 'decoding' do
11
11
  it 'accepts a binary tuple and sets each attribute' do
12
12
  failed_delivery_attempt = described_class.new(binary_tuple)
13
- failed_delivery_attempt.timestamp.should == timestamp
14
- failed_delivery_attempt.device_token.should == device_token
13
+ expect(failed_delivery_attempt.timestamp).to eq(timestamp)
14
+ expect(failed_delivery_attempt.device_token).to eq(device_token)
15
15
  end
16
16
 
17
17
  it 'raises an exception when there are problems decoding' do
@@ -17,45 +17,45 @@ describe Grocer::FeedbackConnection do
17
17
  end
18
18
 
19
19
  it 'can be initialized with a certificate' do
20
- subject.certificate.should == '/path/to/cert.pem'
20
+ expect(subject.certificate).to eq('/path/to/cert.pem')
21
21
  end
22
22
 
23
23
  it 'can be initialized with a passphrase' do
24
24
  options[:passphrase] = 'open sesame'
25
- subject.passphrase.should == 'open sesame'
25
+ expect(subject.passphrase).to eq('open sesame')
26
26
  end
27
27
 
28
28
  it 'defaults to Apple feedback gateway in production environment' do
29
29
  Grocer.stubs(:env).returns('production')
30
- subject.gateway.should == 'feedback.push.apple.com'
30
+ expect(subject.gateway).to eq('feedback.push.apple.com')
31
31
  end
32
32
 
33
33
  it 'defaults to the sandboxed Apple feedback gateway in development environment' do
34
34
  Grocer.stubs(:env).returns('development')
35
- subject.gateway.should == 'feedback.sandbox.push.apple.com'
35
+ expect(subject.gateway).to eq('feedback.sandbox.push.apple.com')
36
36
  end
37
37
 
38
38
  it 'defaults to the sandboxed Apple feedback gateway in test environment' do
39
39
  Grocer.stubs(:env).returns('test')
40
- subject.gateway.should == 'feedback.sandbox.push.apple.com'
40
+ expect(subject.gateway).to eq('feedback.sandbox.push.apple.com')
41
41
  end
42
42
 
43
43
  it 'defaults to the sandboxed Apple feedback gateway for other random values' do
44
44
  Grocer.stubs(:env).returns('random')
45
- subject.gateway.should == 'feedback.sandbox.push.apple.com'
45
+ expect(subject.gateway).to eq('feedback.sandbox.push.apple.com')
46
46
  end
47
47
 
48
48
  it 'can be initialized with a gateway' do
49
49
  options[:gateway] = 'gateway.example.com'
50
- subject.gateway.should == 'gateway.example.com'
50
+ expect(subject.gateway).to eq('gateway.example.com')
51
51
  end
52
52
 
53
53
  it 'defaults to 2196 as the port' do
54
- subject.port.should == 2196
54
+ expect(subject.port).to eq(2196)
55
55
  end
56
56
 
57
57
  it 'can be initialized with a port' do
58
58
  options[:port] = 443
59
- subject.port.should == 443
59
+ expect(subject.port).to eq(443)
60
60
  end
61
61
  end
@@ -21,7 +21,7 @@ describe Grocer::Feedback do
21
21
  subject { described_class.new(connection) }
22
22
 
23
23
  it 'is enumerable' do
24
- subject.should be_kind_of(Enumerable)
24
+ expect(subject).to be_kind_of(Enumerable)
25
25
  end
26
26
 
27
27
  it 'reads failed delivery attempt messages from the connection' do
@@ -29,10 +29,10 @@ describe Grocer::Feedback do
29
29
 
30
30
  delivery_attempts = subject.to_a
31
31
 
32
- delivery_attempts[0].timestamp.should == jan1
33
- delivery_attempts[0].device_token.should == device_token
32
+ expect(delivery_attempts[0].timestamp).to eq(jan1)
33
+ expect(delivery_attempts[0].device_token).to eq(device_token)
34
34
 
35
- delivery_attempts[1].timestamp.should == jan2
36
- delivery_attempts[1].device_token.should == device_token
35
+ expect(delivery_attempts[1].timestamp).to eq(jan2)
36
+ expect(delivery_attempts[1].device_token).to eq(device_token)
37
37
  end
38
38
  end
@@ -12,7 +12,7 @@ describe Grocer::NotificationReader do
12
12
  io.rewind
13
13
 
14
14
  notification = subject.first
15
- notification.identifier.should == 1234
15
+ expect(notification.identifier).to eq(1234)
16
16
  end
17
17
 
18
18
  it "reads expiry" do
@@ -20,7 +20,7 @@ describe Grocer::NotificationReader do
20
20
  io.rewind
21
21
 
22
22
  notification = subject.first
23
- notification.expiry.should == Time.utc(2013, 3, 24)
23
+ expect(notification.expiry).to eq(Time.utc(2013, 3, 24))
24
24
  end
25
25
 
26
26
  it "reads device token" do
@@ -28,7 +28,7 @@ describe Grocer::NotificationReader do
28
28
  io.rewind
29
29
 
30
30
  notification = subject.first
31
- notification.device_token.should == 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
31
+ expect(notification.device_token).to eq('fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2')
32
32
  end
33
33
 
34
34
  it "reads alert" do
@@ -36,7 +36,7 @@ describe Grocer::NotificationReader do
36
36
  io.rewind
37
37
 
38
38
  notification = subject.first
39
- notification.alert.should == "Foo"
39
+ expect(notification.alert).to eq("Foo")
40
40
  end
41
41
 
42
42
  it "reads badge" do
@@ -44,7 +44,7 @@ describe Grocer::NotificationReader do
44
44
  io.rewind
45
45
 
46
46
  notification = subject.first
47
- notification.badge.should == 5
47
+ expect(notification.badge).to eq(5)
48
48
  end
49
49
 
50
50
  it "reads sound" do
@@ -52,7 +52,7 @@ describe Grocer::NotificationReader do
52
52
  io.rewind
53
53
 
54
54
  notification = subject.first
55
- notification.sound.should == "foo.aiff"
55
+ expect(notification.sound).to eq("foo.aiff")
56
56
  end
57
57
 
58
58
  it "reads custom attributes" do
@@ -60,7 +60,7 @@ describe Grocer::NotificationReader do
60
60
  io.rewind
61
61
 
62
62
  notification = subject.first
63
- notification.custom.should == { foo: "bar" }
63
+ expect(notification.custom).to eq({ foo: "bar" })
64
64
  end
65
65
  end
66
66
  end
@@ -1,86 +1,49 @@
1
1
  # encoding: UTF-8
2
-
3
2
  require 'spec_helper'
4
3
  require 'grocer/notification'
4
+ require 'grocer/shared_examples_for_notifications'
5
5
 
6
6
  describe Grocer::Notification do
7
7
  describe 'binary format' do
8
- let(:notification) { described_class.new(payload_options) }
9
- let(:payload_from_bytes) { notification.to_bytes[45..-1] }
10
- let(:payload_dictionary_from_bytes) { JSON.parse(payload_from_bytes, symbolize_names: true) }
11
8
  let(:payload_options) { { alert: 'hi', badge: 2, sound: 'siren.aiff' } }
9
+ let(:payload_dictionary_from_bytes) { JSON.parse(payload_from_bytes, symbolize_names: true) }
10
+ let(:payload_from_bytes) { notification.to_bytes[45..-1] }
12
11
 
13
- subject(:bytes) { notification.to_bytes }
14
-
15
- it 'sets the command byte to 1' do
16
- bytes[0].should == "\x01"
17
- end
18
-
19
- it 'defaults the identifer to 0' do
20
- bytes[1...5].should == "\x00\x00\x00\x00"
21
- end
22
-
23
- it 'allows the identifier to be set' do
24
- notification.identifier = 1234
25
- bytes[1...5].should == [1234].pack('N')
26
- end
27
-
28
- it 'defaults expiry to zero' do
29
- bytes[5...9].should == "\x00\x00\x00\x00"
30
- end
31
-
32
- it 'allows the expiry to be set' do
33
- expiry = notification.expiry = Time.utc(2013, 3, 24, 12, 34, 56)
34
- bytes[5...9].should == [expiry.to_i].pack('N')
35
- end
36
-
37
- it 'encodes the device token length as 32' do
38
- bytes[9...11].should == "\x00\x20"
39
- end
40
-
41
- it 'encodes the device token as a 256-bit integer' do
42
- token = notification.device_token = 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
43
- bytes[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
44
- end
45
-
46
- it 'as a convenience, flattens the device token to remove spaces' do
47
- token = notification.device_token = 'fe15 a27d 5df3c3 4778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
48
- bytes[11...43].should == ['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*')
49
- end
50
-
51
- it 'encodes the payload length' do
52
- notification.alert = 'Hello World!'
53
- bytes[43...45].should == [payload_from_bytes.bytesize].pack('n')
54
- end
12
+ include_examples 'a notification'
55
13
 
56
14
  it 'encodes alert as part of the payload' do
57
15
  notification.alert = 'Hello World!'
58
- payload_dictionary_from_bytes[:aps][:alert].should == 'Hello World!'
16
+ expect(payload_dictionary_from_bytes[:aps][:alert]).to eq('Hello World!')
59
17
  end
60
18
 
61
19
  it 'encodes badge as part of the payload' do
62
20
  notification.badge = 42
63
- payload_dictionary_from_bytes[:aps][:badge].should == 42
21
+ expect(payload_dictionary_from_bytes[:aps][:badge]).to eq(42)
64
22
  end
65
23
 
66
24
  it 'encodes sound as part of the payload' do
67
25
  notification.sound = 'siren.aiff'
68
- payload_dictionary_from_bytes[:aps][:sound].should == 'siren.aiff'
26
+ expect(payload_dictionary_from_bytes[:aps][:sound]).to eq('siren.aiff')
69
27
  end
70
28
 
71
29
  it 'encodes custom payload attributes' do
72
30
  notification.custom = { :foo => 'bar' }
73
- payload_dictionary_from_bytes[:foo].should == 'bar'
31
+ expect(payload_dictionary_from_bytes[:foo]).to eq('bar')
74
32
  end
75
33
 
76
34
  it 'encodes UTF-8 characters' do
77
35
  notification.alert = '私'
78
- payload_dictionary_from_bytes[:aps][:alert].force_encoding("UTF-8").should == '私'
36
+ expect(payload_dictionary_from_bytes[:aps][:alert].force_encoding("UTF-8")).to eq('私')
37
+ end
38
+
39
+ it 'encodes the payload length' do
40
+ notification.alert = 'Hello World!'
41
+ expect(bytes[43...45]).to eq([payload_from_bytes.bytesize].pack('n'))
79
42
  end
80
43
 
81
44
  it 'encodes the payload length correctly for multibyte UTF-8 strings' do
82
45
  notification.alert = '私'
83
- bytes[43...45].should == [payload_from_bytes.bytesize].pack('n')
46
+ expect(bytes[43...45]).to eq([payload_from_bytes.bytesize].pack('n'))
84
47
  end
85
48
 
86
49
  context 'invalid payload' do
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'grocer/passbook_notification'
3
+ require 'grocer/shared_examples_for_notifications'
4
+
5
+ describe Grocer::PassbookNotification do
6
+ describe 'binary format' do
7
+ let(:payload_options) { Hash.new }
8
+ let(:payload_dictionary_from_bytes) { JSON.parse(payload_from_bytes, symbolize_names: true) }
9
+ let(:payload_from_bytes) { notification.to_bytes[45..-1] }
10
+
11
+ include_examples 'a notification'
12
+
13
+ it 'does not require a payload' do
14
+ expect(payload_dictionary_from_bytes[:aps]).to be_empty
15
+ end
16
+
17
+ it 'encodes the payload length' do
18
+ payload_length = bytes[43...45].to_i
19
+ expect(payload_length).to be_zero
20
+ end
21
+
22
+ end
23
+ end
@@ -17,46 +17,51 @@ describe Grocer::PushConnection do
17
17
  end
18
18
 
19
19
  it 'can be initialized with a certificate' do
20
- subject.certificate.should == '/path/to/cert.pem'
20
+ expect(subject.certificate).to eq('/path/to/cert.pem')
21
21
  end
22
22
 
23
23
  it 'can be initialized with a passphrase' do
24
24
  options[:passphrase] = 'open sesame'
25
- subject.passphrase.should == 'open sesame'
25
+ expect(subject.passphrase).to eq('open sesame')
26
26
  end
27
27
 
28
28
  it 'defaults to Apple push gateway in production environment' do
29
29
  Grocer.stubs(:env).returns('production')
30
- subject.gateway.should == 'gateway.push.apple.com'
30
+ expect(subject.gateway).to eq('gateway.push.apple.com')
31
31
  end
32
32
 
33
33
  it 'defaults to the sandboxed Apple push gateway in development environment' do
34
34
  Grocer.stubs(:env).returns('development')
35
- subject.gateway.should == 'gateway.sandbox.push.apple.com'
35
+ expect(subject.gateway).to eq('gateway.sandbox.push.apple.com')
36
36
  end
37
37
 
38
38
  it 'defaults to the localhost Apple push gateway in test environment' do
39
39
  Grocer.stubs(:env).returns('test')
40
- subject.gateway.should == '127.0.0.1'
40
+ expect(subject.gateway).to eq('127.0.0.1')
41
+ end
42
+
43
+ it 'uses a case-insensitive environment to determine the push gateway' do
44
+ Grocer.stubs(:env).returns('TEST')
45
+ expect(subject.gateway).to eq('127.0.0.1')
41
46
  end
42
47
 
43
48
  it 'defaults to the sandboxed Apple push gateway for other random values' do
44
49
  Grocer.stubs(:env).returns('random')
45
- subject.gateway.should == 'gateway.sandbox.push.apple.com'
50
+ expect(subject.gateway).to eq('gateway.sandbox.push.apple.com')
46
51
  end
47
52
 
48
53
  it 'can be initialized with a gateway' do
49
54
  options[:gateway] = 'gateway.example.com'
50
- subject.gateway.should == 'gateway.example.com'
55
+ expect(subject.gateway).to eq('gateway.example.com')
51
56
  end
52
57
 
53
58
  it 'defaults to 2195 as the port' do
54
- subject.port.should == 2195
59
+ expect(subject.port).to eq(2195)
55
60
  end
56
61
 
57
62
  it 'can be initialized with a port' do
58
63
  options[:port] = 443
59
- subject.port.should == 443
64
+ expect(subject.port).to eq(443)
60
65
  end
61
66
 
62
67
  end
@@ -25,7 +25,7 @@ describe Grocer::Server do
25
25
  subject.accept
26
26
  Timeout.timeout(5) {
27
27
  notification = subject.notifications.pop
28
- notification.alert.should == "Hi!"
28
+ expect(notification.alert).to eq("Hi!")
29
29
  }
30
30
  end
31
31
 
@@ -0,0 +1,41 @@
1
+ shared_examples 'a notification' do
2
+ let(:notification) { described_class.new(payload_options) }
3
+
4
+ subject(:bytes) { notification.to_bytes }
5
+
6
+ it 'sets the command byte to 1' do
7
+ expect(bytes[0]).to eq("\x01")
8
+ end
9
+
10
+ it 'defaults the identifer to 0' do
11
+ expect(bytes[1...5]).to eq("\x00\x00\x00\x00")
12
+ end
13
+
14
+ it 'allows the identifier to be set' do
15
+ notification.identifier = 1234
16
+ expect(bytes[1...5]).to eq([1234].pack('N'))
17
+ end
18
+
19
+ it 'defaults expiry to zero' do
20
+ expect(bytes[5...9]).to eq("\x00\x00\x00\x00")
21
+ end
22
+
23
+ it 'allows the expiry to be set' do
24
+ expiry = notification.expiry = Time.utc(2013, 3, 24, 12, 34, 56)
25
+ expect(bytes[5...9]).to eq([expiry.to_i].pack('N'))
26
+ end
27
+
28
+ it 'encodes the device token length as 32' do
29
+ expect(bytes[9...11]).to eq("\x00\x20")
30
+ end
31
+
32
+ it 'encodes the device token as a 256-bit integer' do
33
+ token = notification.device_token = 'fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
34
+ expect(bytes[11...43]).to eq(['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*'))
35
+ end
36
+
37
+ it 'as a convenience, flattens the device token to remove spaces' do
38
+ token = notification.device_token = 'fe15 a27d 5df3c3 4778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'
39
+ expect(bytes[11...43]).to eq(['fe15a27d5df3c34778defb1f4f3880265cc52c0c047682223be59fb68500a9a2'].pack('H*'))
40
+ end
41
+ end
@@ -35,7 +35,7 @@ describe Grocer::SSLConnection do
35
35
  }
36
36
 
37
37
  it 'is initialized with a certificate IO' do
38
- subject.certificate.should == File.read(connection_options[:certificate])
38
+ expect(subject.certificate).to eq(File.read(connection_options[:certificate]))
39
39
  end
40
40
  end
41
41
 
@@ -43,19 +43,19 @@ describe Grocer::SSLConnection do
43
43
 
44
44
  describe 'configuration' do
45
45
  it 'is initialized with a certificate' do
46
- subject.certificate.should == connection_options[:certificate]
46
+ expect(subject.certificate).to eq(connection_options[:certificate])
47
47
  end
48
48
 
49
49
  it 'is initialized with a passphrase' do
50
- subject.passphrase.should == connection_options[:passphrase]
50
+ expect(subject.passphrase).to eq(connection_options[:passphrase])
51
51
  end
52
52
 
53
53
  it 'is initialized with a gateway' do
54
- subject.gateway.should == connection_options[:gateway]
54
+ expect(subject.gateway).to eq(connection_options[:gateway])
55
55
  end
56
56
 
57
57
  it 'is initialized with a port' do
58
- subject.port.should == connection_options[:port]
58
+ expect(subject.port).to eq(connection_options[:port])
59
59
  end
60
60
  end
61
61
 
@@ -16,7 +16,7 @@ describe Grocer::SSLServer do
16
16
  end
17
17
 
18
18
  it "is constructed with a port option" do
19
- subject.port.should == 12345
19
+ expect(subject.port).to eq(12345)
20
20
  end
21
21
 
22
22
 
@@ -25,7 +25,7 @@ describe Grocer::SSLServer do
25
25
  clients = []
26
26
  subject.accept { |c| clients << c }
27
27
 
28
- clients.should == [mock_client]
28
+ expect(clients).to eq([mock_client])
29
29
  end
30
30
  end
31
31
 
data/spec/grocer_spec.rb CHANGED
@@ -12,17 +12,17 @@ describe Grocer do
12
12
  end
13
13
 
14
14
  it 'defaults to development' do
15
- subject.env.should == 'development'
15
+ expect(subject.env).to eq('development')
16
16
  end
17
17
 
18
18
  it 'reads RAILS_ENV from ENV' do
19
19
  ENV.stubs(:[]).with('RAILS_ENV').returns('staging')
20
- subject.env.should == 'staging'
20
+ expect(subject.env).to eq('staging')
21
21
  end
22
22
 
23
23
  it 'reads RACK_ENV from ENV' do
24
24
  ENV.stubs(:[]).with('RACK_ENV').returns('staging')
25
- subject.env.should == 'staging'
25
+ expect(subject.env).to eq('staging')
26
26
  end
27
27
  end
28
28
 
@@ -35,7 +35,7 @@ describe Grocer do
35
35
  end
36
36
 
37
37
  it 'gets a Pusher' do
38
- subject.pusher(connection_options).should be_a Grocer::Pusher
38
+ expect(subject.pusher(connection_options)).to be_a Grocer::Pusher
39
39
  end
40
40
 
41
41
  it 'passes the connection options on to the underlying Connection' do
@@ -50,7 +50,7 @@ describe Grocer do
50
50
  end
51
51
 
52
52
  it 'gets Feedback' do
53
- subject.feedback(connection_options).should be_a Grocer::Feedback
53
+ expect(subject.feedback(connection_options)).to be_a Grocer::Feedback
54
54
  end
55
55
 
56
56
  it 'passes the connection options on to the underlying Connection' do
@@ -66,7 +66,7 @@ describe Grocer do
66
66
  end
67
67
 
68
68
  it 'gets Server' do
69
- subject.server(connection_options).should be_a Grocer::Server
69
+ expect(subject.server(connection_options)).to be_a Grocer::Server
70
70
  end
71
71
 
72
72
  it 'passes the connection options on to the underlying SSLServer' do
@@ -76,5 +76,4 @@ describe Grocer do
76
76
  end
77
77
 
78
78
  end
79
-
80
79
  end
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  ENV['RACK_ENV'] = 'test'
2
- require 'mocha'
2
+ require 'mocha/api'
3
3
  require 'bourne'
4
4
 
5
5
  RSpec.configure do |config|
metadata CHANGED
@@ -1,8 +1,8 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: grocer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
5
4
  prerelease:
5
+ version: 0.3.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Andy Lindeman
@@ -11,104 +11,88 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-11-16 00:00:00.000000000 Z
14
+ date: 2012-12-22 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
- name: json
18
- requirement: !ruby/object:Gem::Requirement
19
- none: false
20
- requirements:
21
- - - ! '>='
22
- - !ruby/object:Gem::Version
23
- version: '0'
24
- type: :runtime
25
- prerelease: false
26
17
  version_requirements: !ruby/object:Gem::Requirement
27
- none: false
28
- requirements:
29
- - - ! '>='
30
- - !ruby/object:Gem::Version
31
- version: '0'
32
- - !ruby/object:Gem::Dependency
33
- name: rspec
34
- requirement: !ruby/object:Gem::Requirement
35
- none: false
36
18
  requirements:
37
19
  - - ~>
38
20
  - !ruby/object:Gem::Version
39
21
  version: '2.11'
22
+ none: false
23
+ name: rspec
40
24
  type: :development
41
25
  prerelease: false
42
- version_requirements: !ruby/object:Gem::Requirement
43
- none: false
26
+ requirement: !ruby/object:Gem::Requirement
44
27
  requirements:
45
28
  - - ~>
46
29
  - !ruby/object:Gem::Version
47
30
  version: '2.11'
48
- - !ruby/object:Gem::Dependency
49
- name: pry
50
- requirement: !ruby/object:Gem::Requirement
51
31
  none: false
32
+ - !ruby/object:Gem::Dependency
33
+ version_requirements: !ruby/object:Gem::Requirement
52
34
  requirements:
53
35
  - - ~>
54
36
  - !ruby/object:Gem::Version
55
37
  version: 0.9.8
38
+ none: false
39
+ name: pry
56
40
  type: :development
57
41
  prerelease: false
58
- version_requirements: !ruby/object:Gem::Requirement
59
- none: false
42
+ requirement: !ruby/object:Gem::Requirement
60
43
  requirements:
61
44
  - - ~>
62
45
  - !ruby/object:Gem::Version
63
46
  version: 0.9.8
64
- - !ruby/object:Gem::Dependency
65
- name: mocha
66
- requirement: !ruby/object:Gem::Requirement
67
47
  none: false
48
+ - !ruby/object:Gem::Dependency
49
+ version_requirements: !ruby/object:Gem::Requirement
68
50
  requirements:
69
51
  - - ! '>='
70
52
  - !ruby/object:Gem::Version
71
53
  version: '0'
54
+ none: false
55
+ name: mocha
72
56
  type: :development
73
57
  prerelease: false
74
- version_requirements: !ruby/object:Gem::Requirement
75
- none: false
58
+ requirement: !ruby/object:Gem::Requirement
76
59
  requirements:
77
60
  - - ! '>='
78
61
  - !ruby/object:Gem::Version
79
62
  version: '0'
80
- - !ruby/object:Gem::Dependency
81
- name: bourne
82
- requirement: !ruby/object:Gem::Requirement
83
63
  none: false
64
+ - !ruby/object:Gem::Dependency
65
+ version_requirements: !ruby/object:Gem::Requirement
84
66
  requirements:
85
67
  - - ! '>='
86
68
  - !ruby/object:Gem::Version
87
69
  version: '0'
70
+ none: false
71
+ name: bourne
88
72
  type: :development
89
73
  prerelease: false
90
- version_requirements: !ruby/object:Gem::Requirement
91
- none: false
74
+ requirement: !ruby/object:Gem::Requirement
92
75
  requirements:
93
76
  - - ! '>='
94
77
  - !ruby/object:Gem::Version
95
78
  version: '0'
96
- - !ruby/object:Gem::Dependency
97
- name: rake
98
- requirement: !ruby/object:Gem::Requirement
99
79
  none: false
80
+ - !ruby/object:Gem::Dependency
81
+ version_requirements: !ruby/object:Gem::Requirement
100
82
  requirements:
101
83
  - - ! '>='
102
84
  - !ruby/object:Gem::Version
103
85
  version: '0'
86
+ none: false
87
+ name: rake
104
88
  type: :development
105
89
  prerelease: false
106
- version_requirements: !ruby/object:Gem::Requirement
107
- none: false
90
+ requirement: !ruby/object:Gem::Requirement
108
91
  requirements:
109
92
  - - ! '>='
110
93
  - !ruby/object:Gem::Version
111
94
  version: '0'
95
+ none: false
112
96
  description: ! " Grocer interfaces with the Apple Push\n
113
97
  \ Notification Service to send push\n notifications
114
98
  to iOS devices and collect\n notification feedback via
@@ -145,6 +129,7 @@ files:
145
129
  - lib/grocer/no_port_error.rb
146
130
  - lib/grocer/notification.rb
147
131
  - lib/grocer/notification_reader.rb
132
+ - lib/grocer/passbook_notification.rb
148
133
  - lib/grocer/push_connection.rb
149
134
  - lib/grocer/pusher.rb
150
135
  - lib/grocer/server.rb
@@ -164,9 +149,11 @@ files:
164
149
  - spec/grocer/feedback_spec.rb
165
150
  - spec/grocer/notification_reader_spec.rb
166
151
  - spec/grocer/notification_spec.rb
152
+ - spec/grocer/passbook_notification_spec.rb
167
153
  - spec/grocer/push_connection_spec.rb
168
154
  - spec/grocer/pusher_spec.rb
169
155
  - spec/grocer/server_spec.rb
156
+ - spec/grocer/shared_examples_for_notifications.rb
170
157
  - spec/grocer/ssl_connection_spec.rb
171
158
  - spec/grocer/ssl_server_spec.rb
172
159
  - spec/grocer_spec.rb
@@ -179,20 +166,20 @@ rdoc_options: []
179
166
  require_paths:
180
167
  - lib
181
168
  required_ruby_version: !ruby/object:Gem::Requirement
182
- none: false
183
169
  requirements:
184
170
  - - ! '>='
185
171
  - !ruby/object:Gem::Version
186
172
  version: '0'
187
- required_rubygems_version: !ruby/object:Gem::Requirement
188
173
  none: false
174
+ required_rubygems_version: !ruby/object:Gem::Requirement
189
175
  requirements:
190
176
  - - ! '>='
191
177
  - !ruby/object:Gem::Version
192
178
  version: '0'
179
+ none: false
193
180
  requirements: []
194
181
  rubyforge_project:
195
- rubygems_version: 1.8.24
182
+ rubygems_version: 1.8.23
196
183
  signing_key:
197
184
  specification_version: 3
198
185
  summary: Pushing Apple notifications since 2012.
@@ -207,10 +194,13 @@ test_files:
207
194
  - spec/grocer/feedback_spec.rb
208
195
  - spec/grocer/notification_reader_spec.rb
209
196
  - spec/grocer/notification_spec.rb
197
+ - spec/grocer/passbook_notification_spec.rb
210
198
  - spec/grocer/push_connection_spec.rb
211
199
  - spec/grocer/pusher_spec.rb
212
200
  - spec/grocer/server_spec.rb
201
+ - spec/grocer/shared_examples_for_notifications.rb
213
202
  - spec/grocer/ssl_connection_spec.rb
214
203
  - spec/grocer/ssl_server_spec.rb
215
204
  - spec/grocer_spec.rb
216
205
  - spec/spec_helper.rb
206
+ has_rdoc: