rpush 5.2.0 → 6.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (84) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +59 -0
  3. data/README.md +53 -8
  4. data/lib/generators/templates/add_adm.rb +1 -1
  5. data/lib/generators/templates/add_alert_is_json_to_rapns_notifications.rb +2 -2
  6. data/lib/generators/templates/add_app_to_rapns.rb +2 -2
  7. data/lib/generators/templates/add_fail_after_to_rpush_notifications.rb +1 -1
  8. data/lib/generators/templates/add_gcm.rb +11 -25
  9. data/lib/generators/templates/add_rpush.rb +33 -83
  10. data/lib/generators/templates/add_wpns.rb +1 -1
  11. data/lib/generators/templates/create_rapns_apps.rb +1 -1
  12. data/lib/generators/templates/create_rapns_feedback.rb +3 -9
  13. data/lib/generators/templates/create_rapns_notifications.rb +3 -9
  14. data/lib/generators/templates/rename_rapns_to_rpush.rb +9 -33
  15. data/lib/generators/templates/rpush.rb +1 -4
  16. data/lib/generators/templates/rpush_2_0_0_updates.rb +5 -17
  17. data/lib/generators/templates/rpush_2_1_0_updates.rb +1 -1
  18. data/lib/generators/templates/rpush_2_6_0_updates.rb +1 -1
  19. data/lib/generators/templates/rpush_2_7_0_updates.rb +1 -1
  20. data/lib/generators/templates/rpush_3_0_0_updates.rb +1 -1
  21. data/lib/generators/templates/rpush_3_0_1_updates.rb +1 -1
  22. data/lib/generators/templates/rpush_3_1_0_add_pushy.rb +1 -1
  23. data/lib/generators/templates/rpush_3_1_1_updates.rb +1 -1
  24. data/lib/generators/templates/rpush_3_2_0_add_apns_p8.rb +1 -1
  25. data/lib/generators/templates/rpush_3_2_4_updates.rb +1 -1
  26. data/lib/generators/templates/rpush_3_3_0_updates.rb +1 -1
  27. data/lib/generators/templates/rpush_3_3_1_updates.rb +3 -3
  28. data/lib/generators/templates/rpush_4_1_0_updates.rb +1 -1
  29. data/lib/generators/templates/rpush_4_1_1_updates.rb +1 -1
  30. data/lib/generators/templates/rpush_4_2_0_updates.rb +1 -1
  31. data/lib/rpush/client/active_model/adm/data_validator.rb +1 -1
  32. data/lib/rpush/client/active_model/apns/app.rb +1 -17
  33. data/lib/rpush/client/active_model/apns/device_token_format_validator.rb +2 -2
  34. data/lib/rpush/client/active_model/apns/notification.rb +4 -0
  35. data/lib/rpush/client/active_model/apns/notification_payload_size_validator.rb +1 -1
  36. data/lib/rpush/client/active_model/apns2/app.rb +7 -1
  37. data/lib/rpush/client/active_model/certificate_private_key_validator.rb +19 -0
  38. data/lib/rpush/client/active_model/gcm/expiry_collapse_key_mutual_inclusion_validator.rb +1 -1
  39. data/lib/rpush/client/active_model/payload_data_size_validator.rb +1 -1
  40. data/lib/rpush/client/active_model/registration_ids_count_validator.rb +1 -1
  41. data/lib/rpush/client/active_model/webpush/app.rb +41 -0
  42. data/lib/rpush/client/active_model/webpush/notification.rb +66 -0
  43. data/lib/rpush/client/active_model.rb +4 -0
  44. data/lib/rpush/client/active_record/apnsp8/notification.rb +1 -0
  45. data/lib/rpush/client/active_record/webpush/app.rb +11 -0
  46. data/lib/rpush/client/active_record/webpush/notification.rb +12 -0
  47. data/lib/rpush/client/active_record.rb +3 -0
  48. data/lib/rpush/client/redis/apnsp8/notification.rb +2 -0
  49. data/lib/rpush/client/redis/webpush/app.rb +15 -0
  50. data/lib/rpush/client/redis/webpush/notification.rb +15 -0
  51. data/lib/rpush/client/redis.rb +3 -0
  52. data/lib/rpush/configuration.rb +1 -1
  53. data/lib/rpush/daemon/apns2/delivery.rb +8 -1
  54. data/lib/rpush/daemon/apnsp8/delivery.rb +7 -1
  55. data/lib/rpush/daemon/string_helpers.rb +1 -1
  56. data/lib/rpush/daemon/webpush/delivery.rb +114 -0
  57. data/lib/rpush/daemon/webpush.rb +10 -0
  58. data/lib/rpush/daemon.rb +3 -0
  59. data/lib/rpush/version.rb +3 -3
  60. data/spec/functional/apns2_spec.rb +14 -2
  61. data/spec/functional/retry_spec.rb +1 -1
  62. data/spec/functional/webpush_spec.rb +31 -0
  63. data/spec/spec_helper.rb +3 -1
  64. data/spec/support/active_record_setup.rb +4 -3
  65. data/spec/support/config/database.yml +4 -4
  66. data/spec/support/simplecov_helper.rb +1 -1
  67. data/spec/unit/client/active_record/apns/notification_spec.rb +1 -1
  68. data/spec/unit/client/active_record/apns2/app_spec.rb +1 -0
  69. data/spec/unit/client/active_record/apns2/notification_spec.rb +1 -1
  70. data/spec/unit/client/active_record/apnsp8/notification_spec.rb +28 -0
  71. data/spec/unit/client/active_record/webpush/app_spec.rb +6 -0
  72. data/spec/unit/client/active_record/webpush/notification_spec.rb +6 -0
  73. data/spec/unit/client/redis/apns/notification_spec.rb +1 -1
  74. data/spec/unit/client/redis/apns2/notification_spec.rb +1 -1
  75. data/spec/unit/client/redis/apnsp8/notification_spec.rb +29 -0
  76. data/spec/unit/client/redis/webpush/app_spec.rb +5 -0
  77. data/spec/unit/client/redis/webpush/notification_spec.rb +5 -0
  78. data/spec/unit/client/shared/apns/notification.rb +15 -0
  79. data/spec/unit/client/shared/webpush/app.rb +33 -0
  80. data/spec/unit/client/shared/webpush/notification.rb +83 -0
  81. data/spec/unit/daemon/apnsp8/delivery_spec.rb +53 -0
  82. data/spec/unit/daemon/pushy/delivery_spec.rb +5 -3
  83. data/spec/unit/daemon/webpush/delivery_spec.rb +144 -0
  84. metadata +50 -5
@@ -8,6 +8,7 @@ module Rpush
8
8
  class Delivery < Rpush::Daemon::Delivery
9
9
  RETRYABLE_CODES = [ 429, 500, 503 ]
10
10
  CLIENT_JOIN_TIMEOUT = 60
11
+ DEFAULT_MAX_CONCURRENT_STREAMS = 100
11
12
 
12
13
  def initialize(app, http2_client, token_provider, batch)
13
14
  @app = app
@@ -85,7 +86,11 @@ module Rpush
85
86
  def remote_max_concurrent_streams
86
87
  # 0x7fffffff is the default value from http-2 gem (2^31)
87
88
  if @client.remote_settings[:settings_max_concurrent_streams] == 0x7fffffff
88
- 0
89
+ # Ideally we'd fall back to `#local_settings` here, but `NetHttp2::Client`
90
+ # doesn't expose that attr from the `HTTP2::Client` it wraps. Instead, we
91
+ # chose a hard-coded value matching the default local setting from the
92
+ # `HTTP2::Client` class
93
+ DEFAULT_MAX_CONCURRENT_STREAMS
89
94
  else
90
95
  @client.remote_settings[:settings_max_concurrent_streams]
91
96
  end
@@ -144,6 +149,7 @@ module Rpush
144
149
  headers['apns-priority'] = '10'
145
150
  headers['apns-topic'] = @app.bundle_id
146
151
  headers['authorization'] = "bearer #{jwt_token}"
152
+ headers['apns-push-type'] = 'background' if notification.content_available?
147
153
 
148
154
  headers.merge notification_data(notification)[HTTP2_HEADERS_KEY] || {}
149
155
  end
@@ -2,7 +2,7 @@ module Rpush
2
2
  module Daemon
3
3
  module StringHelpers
4
4
  def pluralize(count, singular, plural = nil)
5
- if count == 1 || count =~ /^1(\.0+)?$/
5
+ if count == 1
6
6
  word = singular
7
7
  else
8
8
  word = plural || singular.pluralize
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "webpush"
4
+
5
+ module Rpush
6
+ module Daemon
7
+ module Webpush
8
+
9
+ # Webpush::Request handles all the encryption / signing.
10
+ # We just override #perform to inject the http instance that is managed
11
+ # by Rpush.
12
+ #
13
+ class Request < ::Webpush::Request
14
+ def perform(http)
15
+ req = Net::HTTP::Post.new(uri.request_uri, headers)
16
+ req.body = body
17
+ http.request(uri, req)
18
+ end
19
+ end
20
+
21
+ class Delivery < Rpush::Daemon::Delivery
22
+
23
+ OK = [ 200, 201, 202 ].freeze
24
+ TEMPORARY_FAILURES = [ 429, 500, 502, 503, 504 ].freeze
25
+
26
+ def initialize(app, http, notification, batch)
27
+ @app = app
28
+ @http = http
29
+ @notification = notification
30
+ @batch = batch
31
+ end
32
+
33
+ def perform
34
+ response = send_request
35
+ process_response response
36
+ rescue SocketError, SystemCallError => error
37
+ mark_retryable(@notification, Time.now + 10.seconds, error)
38
+ raise
39
+ rescue StandardError => error
40
+ mark_failed(error)
41
+ raise
42
+ ensure
43
+ @batch.notification_processed
44
+ end
45
+
46
+ private
47
+
48
+ def send_request
49
+ # The initializer is inherited from Webpush::Request and looks like
50
+ # this:
51
+ #
52
+ # initialize(message: '', subscription:, vapid:, **options)
53
+ #
54
+ # where subscription is a hash of :endpoint and :keys, and vapid
55
+ # holds the vapid public and private keys and the :subject (which is
56
+ # an email address).
57
+ Request.new(
58
+ message: @notification.message,
59
+ subscription: @notification.subscription,
60
+ vapid: @app.vapid,
61
+ ttl: @notification.time_to_live,
62
+ urgency: @notification.urgency
63
+ ).perform(@http)
64
+ end
65
+
66
+ def process_response(response)
67
+ case response.code.to_i
68
+ when *OK
69
+ mark_delivered
70
+ when *TEMPORARY_FAILURES
71
+ retry_delivery(response)
72
+ else
73
+ fail_delivery(response)
74
+ end
75
+ end
76
+
77
+ def retry_delivery(response)
78
+ time = deliver_after_header(response)
79
+ if time
80
+ mark_retryable(@notification, time)
81
+ else
82
+ mark_retryable_exponential(@notification)
83
+ end
84
+ log_info("Webpush endpoint responded with a #{response.code} error. #{retry_message}")
85
+ end
86
+
87
+ def fail_delivery(response)
88
+ fail_message = fail_message(response)
89
+ log_error("#{@notification.id} failed: #{fail_message}")
90
+ fail Rpush::DeliveryError.new(response.code.to_i, @notification.id, fail_message)
91
+ end
92
+
93
+ def deliver_after_header(response)
94
+ Rpush::Daemon::RetryHeaderParser.parse(response.header['retry-after'])
95
+ end
96
+
97
+ def retry_message
98
+ deliver_after = @notification.deliver_after.strftime('%Y-%m-%d %H:%M:%S')
99
+ "Notification #{@notification.id} will be retried after #{deliver_after} (retry #{@notification.retries})."
100
+ end
101
+
102
+ def fail_message(response)
103
+ msg = Rpush::Daemon::HTTP_STATUS_CODES[response.code.to_i]
104
+ if explanation = response.body.to_s[0..200].presence
105
+ msg += ": #{explanation}"
106
+ end
107
+ msg
108
+ end
109
+
110
+ end
111
+ end
112
+ end
113
+ end
114
+
@@ -0,0 +1,10 @@
1
+ module Rpush
2
+ module Daemon
3
+ module Webpush
4
+ extend ServiceConfigMethods
5
+
6
+ dispatcher :http
7
+ end
8
+ end
9
+ end
10
+
data/lib/rpush/daemon.rb CHANGED
@@ -68,6 +68,9 @@ require 'rpush/daemon/adm'
68
68
  require 'rpush/daemon/pushy'
69
69
  require 'rpush/daemon/pushy/delivery'
70
70
 
71
+ require 'rpush/daemon/webpush/delivery'
72
+ require 'rpush/daemon/webpush'
73
+
71
74
  module Rpush
72
75
  module Daemon
73
76
  class << self
data/lib/rpush/version.rb CHANGED
@@ -1,8 +1,8 @@
1
1
  module Rpush
2
2
  module VERSION
3
- MAJOR = 5
4
- MINOR = 2
5
- TINY = 0
3
+ MAJOR = 6
4
+ MINOR = 0
5
+ TINY = 1
6
6
  PRE = nil
7
7
 
8
8
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".").freeze
@@ -42,6 +42,7 @@ describe 'APNs http2 adapter' do
42
42
  app.certificate = TEST_CERT
43
43
  app.name = 'test'
44
44
  app.environment = 'development'
45
+ app.bundle_id = 'com.example.app'
45
46
  app.save!
46
47
  app
47
48
  end
@@ -75,7 +76,13 @@ describe 'APNs http2 adapter' do
75
76
  :post,
76
77
  "/3/device/#{fake_device_token}",
77
78
  { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\",\"content-available\":1}}",
78
- headers: {} }
79
+ headers: {
80
+ 'apns-expiration' => '0',
81
+ 'apns-priority' => '10',
82
+ 'apns-topic' => 'com.example.app',
83
+ 'apns-push-type' => 'background'
84
+ }
85
+ }
79
86
  )
80
87
  .and_return(fake_http2_request)
81
88
 
@@ -104,7 +111,12 @@ describe 'APNs http2 adapter' do
104
111
  "/3/device/#{fake_device_token}",
105
112
  { body: "{\"aps\":{\"alert\":\"test\",\"sound\":\"default\","\
106
113
  "\"content-available\":1},\"some_field\":\"some value\"}",
107
- headers: { 'apns-topic' => bundle_id }
114
+ headers: {
115
+ 'apns-topic' => bundle_id,
116
+ 'apns-expiration' => '0',
117
+ 'apns-priority' => '10',
118
+ 'apns-push-type' => 'background'
119
+ }
108
120
  }
109
121
  ).and_return(fake_http2_request)
110
122
 
@@ -39,4 +39,4 @@ describe 'Retries' do
39
39
  notification.reload
40
40
  expect(notification.delivered).to eq(false)
41
41
  end
42
- end
42
+ end if redis?
@@ -0,0 +1,31 @@
1
+ require 'functional_spec_helper'
2
+
3
+ describe 'Webpush' do
4
+ let(:code) { 201 }
5
+ let(:response) { instance_double('Net::HTTPResponse', code: code, body: '') }
6
+ let(:http) { instance_double('Net::HTTP::Persistent', request: response, shutdown: nil) }
7
+ let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
8
+
9
+ let(:device_reg) {
10
+ { endpoint: 'https://webpush-provider.example.org/push/some-id',
11
+ expirationTime: nil,
12
+ keys: {'auth' => 'DgN9EBia1o057BdhCOGURA', 'p256dh' => 'BAtxJ--7vHq9IVm8utUB3peJ4lpxRqk1rukCIkVJOomS83QkCnrQ4EyYQsSaCRgy_c8XPytgXxuyAvRJdnTPK4A'} }
13
+ }
14
+ let(:notification) { Rpush::Webpush::Notification.create!(app: app, registration_ids: [device_reg], data: { message: 'test' }) }
15
+
16
+ before do
17
+ allow(Net::HTTP::Persistent).to receive_messages(new: http)
18
+ end
19
+
20
+ it 'deliveres a notification successfully' do
21
+ expect { Rpush.push }.to change { notification.reload.delivered }.to(true)
22
+ end
23
+
24
+ context 'when delivery failed' do
25
+ let(:code) { 404 }
26
+ it 'marks a notification as failed' do
27
+ expect { Rpush.push }.to change { notification.reload.failed }.to(true)
28
+ end
29
+ end
30
+ end
31
+
data/spec/spec_helper.rb CHANGED
@@ -3,7 +3,7 @@ def client
3
3
  (ENV['CLIENT'] || :active_record).to_sym
4
4
  end
5
5
 
6
- if !ENV['TRAVIS'] || (ENV['TRAVIS'] && ENV['QUALITY'] == 'true')
6
+ if !ENV['CI'] || (ENV['CI'] && ENV['QUALITY'] == 'true')
7
7
  begin
8
8
  require './spec/support/simplecov_helper'
9
9
  include SimpleCovHelper
@@ -46,6 +46,8 @@ path = File.join(File.dirname(__FILE__), 'support')
46
46
  TEST_CERT = File.read(File.join(path, 'cert_without_password.pem'))
47
47
  TEST_CERT_WITH_PASSWORD = File.read(File.join(path, 'cert_with_password.pem'))
48
48
 
49
+ VAPID_KEYPAIR = Webpush.generate_key.to_hash.merge(subject: 'rpush-test@example.org').to_json
50
+
49
51
  def after_example_cleanup
50
52
  Rpush.logger = nil
51
53
  Rpush::Daemon.store = nil
@@ -6,14 +6,15 @@ SPEC_ADAPTER = ENV['ADAPTER'] || 'postgresql'
6
6
  SPEC_ADAPTER = 'jdbc' + SPEC_ADAPTER if jruby
7
7
 
8
8
  require 'yaml'
9
- db_config = YAML.load_file(File.expand_path("config/database.yml", File.dirname(__FILE__)))
9
+ db_config_path = File.expand_path("config/database.yml", File.dirname(__FILE__))
10
+ db_config = YAML.load(ERB.new(File.read(db_config_path)).result)
10
11
 
11
12
  if db_config[SPEC_ADAPTER].nil?
12
13
  puts "No such adapter '#{SPEC_ADAPTER}'. Valid adapters are #{db_config.keys.join(', ')}."
13
14
  exit 1
14
15
  end
15
16
 
16
- if ENV['TRAVIS']
17
+ if ENV['CI']
17
18
  db_config[SPEC_ADAPTER]['username'] = 'postgres'
18
19
  else
19
20
  require 'etc'
@@ -62,7 +63,7 @@ migrations = [
62
63
  Rpush420Updates
63
64
  ]
64
65
 
65
- unless ENV['TRAVIS']
66
+ unless ENV['CI']
66
67
  migrations.reverse_each do |m|
67
68
  begin
68
69
  m.down
@@ -2,10 +2,10 @@
2
2
 
3
3
  postgresql:
4
4
  adapter: postgresql
5
- database: rpush_test
6
- host: localhost
7
- username: postgres
8
- password: ""
5
+ database: <%= ENV.fetch('POSTGRES_DB', 'rpush_test') %>
6
+ host: <%= ENV.fetch('POSTGRES_HOST', 'localhost') %>
7
+ username: <%= ENV.fetch('POSTGRES_USER', 'postgres') %>
8
+ password: <%= ENV.fetch('PGPASSWORD', '') %>
9
9
 
10
10
  jdbcpostgresql:
11
11
  adapter: jdbcpostgresql
@@ -10,7 +10,7 @@ module SimpleCovHelper
10
10
 
11
11
  formatters = [SimpleCov::Formatter::QualityFormatter]
12
12
 
13
- if ENV['TRAVIS']
13
+ if ENV['CI']
14
14
  require 'codeclimate-test-reporter'
15
15
 
16
16
  if CodeClimate::TestReporter.run?
@@ -8,7 +8,7 @@ describe Rpush::Client::ActiveRecord::Apns::Notification do
8
8
 
9
9
  it "should validate the length of the binary conversion of the notification" do
10
10
  notification = described_class.new
11
- notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
11
+ notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
12
12
  notification.device_token = "a" * 108
13
13
  notification.alert = ""
14
14
 
@@ -1,4 +1,5 @@
1
1
  require 'unit_spec_helper'
2
2
 
3
3
  describe Rpush::Client::ActiveRecord::Apns2::App do
4
+ it_behaves_like 'Rpush::Client::Apns::App'
4
5
  end if active_record?
@@ -8,7 +8,7 @@ describe Rpush::Client::ActiveRecord::Apns2::Notification do
8
8
 
9
9
  it "should validate the length of the binary conversion of the notification" do
10
10
  notification = described_class.new
11
- notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
11
+ notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
12
12
  notification.device_token = "a" * 108
13
13
  notification.alert = ""
14
14
 
@@ -0,0 +1,28 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Client::ActiveRecord::Apnsp8::Notification do
4
+ subject(:notification) { described_class.new }
5
+
6
+ it_behaves_like 'Rpush::Client::Apns::Notification'
7
+ it_behaves_like 'Rpush::Client::ActiveRecord::Notification'
8
+
9
+ it "should validate the length of the binary conversion of the notification", :aggregate_failures do
10
+ notification = described_class.new
11
+ notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
12
+ apn_key_id: "2",
13
+ name: 'test',
14
+ environment: 'development',
15
+ team_id: "3",
16
+ bundle_id: "4")
17
+ notification.device_token = "a" * 108
18
+ notification.alert = ""
19
+
20
+ notification.alert << "a" until notification.payload.bytesize == 4096
21
+ expect(notification.valid?).to be_truthy
22
+ expect(notification.errors[:base]).to be_empty
23
+
24
+ notification.alert << "a"
25
+ expect(notification.valid?).to be_falsey
26
+ expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
27
+ end
28
+ end if active_record?
@@ -0,0 +1,6 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::ActiveRecord::Webpush::App do
4
+ it_behaves_like 'Rpush::Client::Webpush::App'
5
+ it_behaves_like 'Rpush::Client::ActiveRecord::App'
6
+ end if active_record?
@@ -0,0 +1,6 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::ActiveRecord::Webpush::Notification do
4
+ it_behaves_like 'Rpush::Client::Webpush::Notification'
5
+ it_behaves_like 'Rpush::Client::ActiveRecord::Notification'
6
+ end if active_record?
@@ -7,7 +7,7 @@ describe Rpush::Client::Redis::Apns::Notification do
7
7
 
8
8
  it "should validate the length of the binary conversion of the notification" do
9
9
  notification = described_class.new
10
- notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
10
+ notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
11
11
  notification.device_token = "a" * 108
12
12
  notification.alert = ""
13
13
 
@@ -7,7 +7,7 @@ describe Rpush::Client::Redis::Apns2::Notification do
7
7
 
8
8
  it "should validate the length of the binary conversion of the notification" do
9
9
  notification = described_class.new
10
- notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development')
10
+ notification.app = Rpush::Apns2::App.create(name: 'test', environment: 'development', certificate: TEST_CERT)
11
11
  notification.device_token = "a" * 108
12
12
  notification.alert = ""
13
13
 
@@ -0,0 +1,29 @@
1
+ require "unit_spec_helper"
2
+
3
+ describe Rpush::Client::Redis::Apnsp8::Notification do
4
+ after do
5
+ Rpush::Apnsp8::App.all.select { |a| a.environment == "development" && a.apn_key == "1" }.each(&:destroy)
6
+ end
7
+
8
+ it_behaves_like "Rpush::Client::Apns::Notification"
9
+
10
+ it "should validate the length of the binary conversion of the notification", :aggregate_failures do
11
+ notification = described_class.new
12
+ notification.app = Rpush::Apnsp8::App.create(apn_key: "1",
13
+ apn_key_id: "2",
14
+ name: 'test',
15
+ environment: 'development',
16
+ team_id: "3",
17
+ bundle_id: "4")
18
+ notification.device_token = "a" * 108
19
+ notification.alert = ""
20
+
21
+ notification.alert << "a" until notification.payload.bytesize == 4096
22
+ expect(notification.valid?).to be_truthy
23
+ expect(notification.errors[:base]).to be_empty
24
+
25
+ notification.alert << "a"
26
+ expect(notification.valid?).to be_falsey
27
+ expect(notification.errors[:base].include?("APN notification cannot be larger than 4096 bytes. Try condensing your alert and device attributes.")).to be_truthy
28
+ end
29
+ end if redis?
@@ -0,0 +1,5 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::Redis::Webpush::App do
4
+ it_behaves_like 'Rpush::Client::Webpush::App'
5
+ end if redis?
@@ -0,0 +1,5 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Client::Redis::Webpush::Notification do
4
+ it_behaves_like 'Rpush::Client::Webpush::Notification'
5
+ end if redis?
@@ -165,6 +165,21 @@ shared_examples 'Rpush::Client::Apns::Notification' do
165
165
  end
166
166
  end
167
167
 
168
+ describe 'content_available?' do
169
+ context 'if not set' do
170
+ it 'should be false' do
171
+ expect(notification.content_available?).to be_falsey
172
+ end
173
+ end
174
+
175
+ context 'if set' do
176
+ it 'should be true' do
177
+ notification.content_available = true
178
+ expect(notification.content_available?).to be_truthy
179
+ end
180
+ end
181
+ end
182
+
168
183
  describe 'url-args' do
169
184
  it 'includes url-args in the payload' do
170
185
  notification.url_args = ['url-arg-1']
@@ -0,0 +1,33 @@
1
+ require 'unit_spec_helper'
2
+
3
+ shared_examples 'Rpush::Client::Webpush::App' do
4
+ describe 'validates' do
5
+ subject { described_class.new }
6
+
7
+ it 'validates presence of name' do
8
+ is_expected.not_to be_valid
9
+ expect(subject.errors[:name]).to eq ["can't be blank"]
10
+ end
11
+
12
+ it 'validates presence of vapid_keypair' do
13
+ is_expected.not_to be_valid
14
+ expect(subject.errors[:vapid_keypair]).to eq ["can't be blank"]
15
+ end
16
+
17
+ it 'should require the vapid keypair to have subject, public and private key' do
18
+ subject.vapid_keypair = {}.to_json
19
+ is_expected.not_to be_valid
20
+ expect(subject.errors[:vapid_keypair].sort).to eq [
21
+ 'must have a private_key entry',
22
+ 'must have a public_key entry',
23
+ 'must have a subject entry',
24
+ ]
25
+ end
26
+
27
+ it 'should require valid json for the keypair' do
28
+ subject.vapid_keypair = 'invalid'
29
+ is_expected.not_to be_valid
30
+ expect(subject.errors[:vapid_keypair].sort).to eq [ 'must be valid JSON' ]
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,83 @@
1
+ require 'unit_spec_helper'
2
+
3
+ shared_examples 'Rpush::Client::Webpush::Notification' do
4
+ subject(:notification) { described_class.new }
5
+
6
+ describe 'notification attributes' do
7
+ describe 'data' do
8
+ subject { described_class.new(data: { message: 'test', urgency: 'normal' } ) }
9
+ it 'has a message' do
10
+ expect(subject.message).to eq "test"
11
+ end
12
+ it 'has an urgency' do
13
+ expect(subject.urgency).to eq "normal"
14
+ end
15
+ end
16
+
17
+ describe 'subscription' do
18
+ let(:subscription){ { endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}} }
19
+ subject { described_class.new(registration_ids: [subscription]) }
20
+
21
+ it "has a subscription" do
22
+ expect(subject.subscription).to eq({ endpoint: 'https://push.example.org/foo', keys: {foo: 'bar'} })
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ describe 'validates' do
29
+ let(:app) { Rpush::Webpush::App.create!(name: 'MyApp', vapid_keypair: VAPID_KEYPAIR) }
30
+
31
+ describe 'data' do
32
+ subject { described_class.new(app: app, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
33
+ it 'validates presence' do
34
+ is_expected.not_to be_valid
35
+ expect(subject.errors[:data]).to eq ["can't be blank"]
36
+ end
37
+
38
+ it "has a 'data' payload limit of 4096 bytes" do
39
+ subject.data = { message: 'a' * 4096 }
40
+ is_expected.not_to be_valid
41
+ expected_errors = ["Notification payload data cannot be larger than 4096 bytes."]
42
+ expect(subject.errors[:base]).to eq expected_errors
43
+ end
44
+ end
45
+
46
+ describe 'registration_ids' do
47
+ subject { described_class.new(app: app, data: { message: 'test' }) }
48
+ it 'validates presence' do
49
+ is_expected.not_to be_valid
50
+ expect(subject.errors[:registration_ids]).to eq ["can't be blank"]
51
+ end
52
+
53
+ it 'limits the number of registration ids to exactly 1' do
54
+ subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}] * 2
55
+ is_expected.not_to be_valid
56
+ expected_errors = ["Number of registration_ids cannot be larger than 1."]
57
+ expect(subject.errors[:base]).to eq expected_errors
58
+ end
59
+
60
+ it 'validates the structure of the registration' do
61
+ subject.registration_ids = ['a']
62
+ is_expected.not_to be_valid
63
+ expect(subject.errors[:base]).to eq [
64
+ "Registration must have :endpoint (String) and :keys (Hash) keys"
65
+ ]
66
+
67
+ subject.registration_ids = [{endpoint: 'string', keys: { 'a' => 'hash' }}]
68
+ is_expected.to be_valid
69
+ end
70
+ end
71
+
72
+ describe 'time_to_live' do
73
+ subject { described_class.new(app: app, data: { message: 'test' }, registration_ids: [{endpoint: 'https://push.example.org/foo', keys: {'foo' => 'bar'}}]) }
74
+
75
+ it 'should be > 0' do
76
+ subject.time_to_live = -1
77
+ is_expected.not_to be_valid
78
+ expect(subject.errors[:time_to_live]).to eq ['must be greater than 0']
79
+ end
80
+ end
81
+
82
+ end
83
+ end
@@ -0,0 +1,53 @@
1
+ require 'unit_spec_helper'
2
+
3
+ describe Rpush::Daemon::Apnsp8::Delivery do
4
+ subject(:delivery) { described_class.new(app, http2_client, token_provider, batch) }
5
+
6
+ let(:app) { double(bundle_id: 'MY BUNDLE ID') }
7
+ let(:notification1) { double('Notification 1', data: {}, as_json: {}).as_null_object }
8
+ let(:notification2) { double('Notification 2', data: {}, as_json: {}).as_null_object }
9
+
10
+ let(:token_provider) { double(token: 'MY JWT TOKEN') }
11
+ let(:max_concurrent_streams) { 100 }
12
+ let(:remote_settings) { { settings_max_concurrent_streams: max_concurrent_streams } }
13
+ let(:http_request) { double(on: nil) }
14
+ let(:http2_client) do
15
+ double(
16
+ stream_count: 0,
17
+ call_async: nil,
18
+ join: nil,
19
+ prepare_request: http_request,
20
+ remote_settings: remote_settings
21
+ )
22
+ end
23
+
24
+ let(:batch) { double(mark_delivered: nil, all_processed: nil) }
25
+ let(:logger) { double(info: nil) }
26
+
27
+ before do
28
+ allow(batch).to receive(:each_notification) do |&blk|
29
+ [notification1, notification2].each(&blk)
30
+ end
31
+ allow(Rpush).to receive_messages(logger: logger)
32
+ end
33
+
34
+ describe '#perform' do
35
+ context 'with an HTTP2 client where max concurrent streams is not set' do
36
+ let(:max_concurrent_streams) { 0x7fffffff }
37
+
38
+ it 'does not fall into an infinite loop on notifications after the first' do
39
+ start = Time.now
40
+ thread = Thread.new { delivery.perform }
41
+
42
+ loop do
43
+ break unless thread.alive?
44
+
45
+ if Time.now - start > 0.5
46
+ thread.kill
47
+ fail 'Stuck in an infinite loop'
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end