grocer 0.2.0 → 0.3.0

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