Have specs run without Redis and refactor ConsumerApp and Device (#13647)
* Add fakeredis gem * Mock Pusher::PushNotifications in specs * Remove Redis from Travis * Add Android tests to ConsumerApp specs * Use mock for push notification tests and refactor ConsumerApp and Device code * Fix remaining broken specs * Use symbols * Cleanup rpush helpers * Pusher::PushNotifications is no more * Use human friendly platform in Admin::ConsumerApps index page
This commit is contained in:
parent
de488f331d
commit
cf9f6094cd
24 changed files with 241 additions and 97 deletions
|
|
@ -18,8 +18,6 @@ addons:
|
|||
- $(ls cypress/screenshots/*.* | tr "\n" ":")
|
||||
- $(ls cypress/videos/*.* | tr "\n" ":")
|
||||
debug: true
|
||||
services:
|
||||
- redis
|
||||
env:
|
||||
global:
|
||||
- RAILS_ENV=test
|
||||
|
|
|
|||
1
Gemfile
1
Gemfile
|
|
@ -159,6 +159,7 @@ end
|
|||
group :test do
|
||||
gem "exifr", ">= 1.3.6" # EXIF Reader is a module to read EXIF from JPEG and TIFF images
|
||||
gem "factory_bot_rails", "~> 6.1" # factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies
|
||||
gem "fakeredis", "~> 0.8.0" # Fake (In-memory) driver for redis-rb. Useful for testing environment and machines without Redis.
|
||||
gem "launchy", "~> 2.5" # Launchy is helper class for launching cross-platform applications in a fire and forget manner.
|
||||
gem "pundit-matchers", "~> 1.6" # A set of RSpec matchers for testing Pundit authorisation policies
|
||||
gem "rspec-retry", "~> 0.6" # retry intermittently failing rspec examples
|
||||
|
|
|
|||
|
|
@ -253,6 +253,8 @@ GEM
|
|||
railties (>= 5.0.0)
|
||||
faker (2.17.0)
|
||||
i18n (>= 1.6, < 2)
|
||||
fakeredis (0.8.0)
|
||||
redis (~> 4.1)
|
||||
faraday (1.4.1)
|
||||
faraday-excon (~> 1.1)
|
||||
faraday-net_http (~> 1.0)
|
||||
|
|
@ -878,6 +880,7 @@ DEPENDENCIES
|
|||
exifr (>= 1.3.6)
|
||||
factory_bot_rails (~> 6.1)
|
||||
faker (~> 2.17)
|
||||
fakeredis (~> 0.8.0)
|
||||
fastly (~> 3.0)
|
||||
feedjira (~> 3.1)
|
||||
field_test (~> 0.4)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ class DevicesController < ApplicationController
|
|||
# See: https://github.com/forem/forem/pull/12419/files#r563906038
|
||||
skip_before_action :verify_authenticity_token, only: [:destroy]
|
||||
|
||||
rescue_from ActiveRecord::ActiveRecordError do |exc|
|
||||
rescue_from ActiveRecord::ActiveRecordError, ArgumentError do |exc|
|
||||
render json: { error: exc.message, status: 422 }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ class ConsumerApp < ApplicationRecord
|
|||
resourcify
|
||||
|
||||
FOREM_BUNDLE = "com.forem.app".freeze
|
||||
SUPPORTED_PLATFORMS = [Device::ANDROID, Device::IOS].freeze
|
||||
FOREM_APP_PLATFORMS = [Device::IOS].freeze
|
||||
FOREM_APP_PLATFORMS = %w[ios].freeze
|
||||
|
||||
validates :app_bundle, presence: true
|
||||
validates :platform, presence: true
|
||||
validates :app_bundle, uniqueness: { scope: :platform }
|
||||
enum platform: { android: Device::ANDROID, ios: Device::IOS }
|
||||
|
||||
validates :app_bundle, presence: true, uniqueness: { scope: :platform }
|
||||
validates :platform, inclusion: { in: platforms.keys }
|
||||
|
||||
has_many :devices, dependent: :destroy
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ class ConsumerApp < ApplicationRecord
|
|||
# https://github.com/rpush/rpush/wiki/Using-Redis#find_by_name-cannot-be-used-in-rpush-redis
|
||||
# rubocop:disable Rails/FindBy
|
||||
def clear_rpush_app
|
||||
case platform_was
|
||||
case ConsumerApp.platforms[platform_was]
|
||||
when Device::IOS
|
||||
Rpush::Apns2::App.where(name: app_bundle_was).first&.destroy
|
||||
when Device::ANDROID
|
||||
|
|
|
|||
|
|
@ -2,21 +2,23 @@ class Device < ApplicationRecord
|
|||
# @fdoxyz to remove app_bundle from Device soon
|
||||
self.ignored_columns = ["app_bundle"]
|
||||
|
||||
belongs_to :user
|
||||
belongs_to :consumer_app
|
||||
belongs_to :user
|
||||
|
||||
IOS = "iOS".freeze
|
||||
ANDROID = "Android".freeze
|
||||
|
||||
enum platform: { android: ANDROID, ios: IOS }
|
||||
|
||||
validates :platform, inclusion: { in: platforms.keys }
|
||||
validates :token, presence: true
|
||||
validates :token, uniqueness: { scope: %i[user_id platform consumer_app_id] }
|
||||
validates :platform, inclusion: { in: [IOS, ANDROID] }
|
||||
|
||||
def create_notification(title, body, payload)
|
||||
case platform
|
||||
when IOS
|
||||
ios_notification(title, body, payload)
|
||||
when ANDROID
|
||||
if android?
|
||||
android_notification(title, body, payload)
|
||||
elsif ios?
|
||||
ios_notification(title, body, payload)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -11,16 +11,24 @@ module ConsumerApps
|
|||
|
||||
def call
|
||||
# This guard clause handles the Forem app special case
|
||||
return forem_app_target if @app_bundle == ConsumerApp::FOREM_BUNDLE
|
||||
return forem_consumer_app if @app_bundle == ConsumerApp::FOREM_BUNDLE
|
||||
|
||||
# All other ConsumerApp are simply fetched as usual
|
||||
ConsumerApp.find_by(app_bundle: @app_bundle, platform: @platform)
|
||||
relation.find_by(app_bundle: @app_bundle)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def forem_app_target
|
||||
ConsumerApp.create_or_find_by(app_bundle: ConsumerApp::FOREM_BUNDLE, platform: @platform)
|
||||
attr_reader :platform
|
||||
|
||||
def relation
|
||||
# taking advantage of enum scopes in Rails
|
||||
# see https://api.rubyonrails.org/classes/ActiveRecord/Enum.html
|
||||
ConsumerApp.public_send(platform)
|
||||
end
|
||||
|
||||
def forem_consumer_app
|
||||
relation.create_or_find_by(app_bundle: ConsumerApp::FOREM_BUNDLE)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ module ConsumerApps
|
|||
def initialize(app_bundle:, platform:)
|
||||
@app_bundle = app_bundle
|
||||
@platform = platform
|
||||
|
||||
@consumer_app = ConsumerApps::FindOrCreateByQuery.call(
|
||||
app_bundle: @app_bundle,
|
||||
platform: @platform,
|
||||
|
|
@ -14,25 +15,26 @@ module ConsumerApps
|
|||
end
|
||||
|
||||
def call
|
||||
case @consumer_app&.platform
|
||||
when Device::IOS
|
||||
ios_app = Rpush::Apns2::App.where(name: @app_bundle).first
|
||||
ios_app || recreate_ios_app!
|
||||
when Device::ANDROID
|
||||
android_app = Rpush::Gcm::App.where(name: @app_bundle).first
|
||||
if consumer_app.android?
|
||||
android_app = Rpush::Gcm::App.where(name: app_bundle).first
|
||||
android_app || recreate_android_app!
|
||||
elsif consumer_app.ios?
|
||||
ios_app = Rpush::Apns2::App.where(name: app_bundle).first
|
||||
ios_app || recreate_ios_app!
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :app_bundle, :consumer_app
|
||||
|
||||
def recreate_ios_app!
|
||||
app = Rpush::Apns2::App.new
|
||||
app.name = @app_bundle
|
||||
app.certificate = @consumer_app.auth_credentials.to_s.gsub("\\n", "\n")
|
||||
app.name = app_bundle
|
||||
app.certificate = consumer_app.auth_credentials.to_s.gsub("\\n", "\n")
|
||||
app.environment = Rails.env
|
||||
app.password = ""
|
||||
app.bundle_id = @app_bundle
|
||||
app.bundle_id = app_bundle
|
||||
app.connections = 1
|
||||
app.save!
|
||||
app
|
||||
|
|
@ -40,8 +42,8 @@ module ConsumerApps
|
|||
|
||||
def recreate_android_app!
|
||||
app = Rpush::Gcm::App.new
|
||||
app.name = @app_bundle
|
||||
app.auth_key = @consumer_app.auth_credentials.to_s
|
||||
app.name = app_bundle
|
||||
app.auth_key = consumer_app.auth_credentials.to_s
|
||||
app.connections = 1
|
||||
app.save!
|
||||
app
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ module PushNotifications
|
|||
end
|
||||
|
||||
def call
|
||||
Device.where(user_id: @user_ids).find_each do |device|
|
||||
relation = Device.where(user_id: @user_ids)
|
||||
|
||||
relation.find_each do |device|
|
||||
device.create_notification(@title, @body, @payload)
|
||||
end
|
||||
|
||||
|
|
@ -21,7 +23,7 @@ module PushNotifications
|
|||
# has a constraint that will execute the job only once (30 seconds from now). So if we need to send 1 PN
|
||||
# every 5 seconds we only execute this once every 30s (no duplicate/unnecessary processing).
|
||||
# If no PNs are scheduled to be sent for a 6h span then 0 jobs are executed.
|
||||
PushNotifications::DeliverWorker.perform_in(30.seconds)
|
||||
PushNotifications::DeliverWorker.perform_in(30.seconds) if relation.any?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
<div class="form-group">
|
||||
<%= label_tag :platform, "Platform:" %>
|
||||
<%= select_tag :platform, options_for_select(ConsumerApp::SUPPORTED_PLATFORMS, selected: @app.platform, class: "crayons-select"), class: "crayons-select" %>
|
||||
<%= select_tag :platform, options_for_select(ConsumerApp.platforms.invert, selected: @app.platform, class: "crayons-select"), class: "crayons-select" %>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
<% @apps.each do |app| %>
|
||||
<tr>
|
||||
<td><%= app.app_bundle %></td>
|
||||
<td><%= app.platform %></td>
|
||||
<td><%= Device.platforms[app.platform] %></td>
|
||||
<td><%= app.devices.count %></td>
|
||||
<% if app.auth_credentials.present? %>
|
||||
<td><span class="crayons-icon" role="img" aria-label="Authentication Key is available">✅</span></td>
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ Rpush.reflect do |on|
|
|||
if notification.error_description == "BadDeviceToken"
|
||||
# When adding new platforms we'll need to check to only clear out devices
|
||||
# scoped to that specific platform. i.e. notification.class.to_s =~ /Apns.+::Notification/
|
||||
Device.where(token: notification.device_token, platform: "iOS").destroy_all
|
||||
Device.ios.where(token: notification.device_token).destroy_all
|
||||
end
|
||||
|
||||
HoneyBadger.notify(error_message:
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ FactoryBot.define do
|
|||
factory :consumer_app do
|
||||
auth_key { Faker::Alphanumeric.alpha(number: 10) }
|
||||
active { true }
|
||||
platform { Device::IOS }
|
||||
platform { :ios }
|
||||
app_bundle { Faker::Internet.domain_name(subdomain: true) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -3,6 +3,6 @@ FactoryBot.define do
|
|||
association :user, strategy: :create
|
||||
association :consumer_app, strategy: :create
|
||||
sequence(:token) { |n| "unique_token_#{n}" }
|
||||
platform { Device::IOS }
|
||||
platform { :ios }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,60 +1,103 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe ConsumerApp, type: :model do
|
||||
let!(:consumer_app) { create(:consumer_app, platform: Device::IOS) }
|
||||
let(:consumer_app_android) { create(:consumer_app, platform: :android) }
|
||||
let(:consumer_app_ios) { create(:consumer_app, platform: :ios) }
|
||||
|
||||
describe "validations" do
|
||||
subject { consumer_app_android }
|
||||
|
||||
describe "builtin validations" do
|
||||
it { is_expected.to have_many(:devices).dependent(:destroy) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:app_bundle) }
|
||||
it { is_expected.to validate_uniqueness_of(:app_bundle).scoped_to(:platform) }
|
||||
end
|
||||
end
|
||||
|
||||
describe "operational?" do
|
||||
context "with non-forem apps" do
|
||||
it "returns false if not active in DB or credentials are unavailable" do
|
||||
context "with non-Forem apps" do
|
||||
it "returns false if not active in DB or if credentials are unavailable" do
|
||||
inactive_consumer_app = create(:consumer_app, active: false)
|
||||
expect(inactive_consumer_app.operational?).to be false
|
||||
expect(inactive_consumer_app.operational?).to be(false)
|
||||
|
||||
consumer_app_without_credentials = create(:consumer_app, auth_key: nil)
|
||||
expect(consumer_app_without_credentials.operational?).to be false
|
||||
expect(consumer_app_without_credentials.operational?).to be(false)
|
||||
end
|
||||
|
||||
it "returns true if both active && credentials are available" do
|
||||
expect(consumer_app.operational?).to be true
|
||||
it "returns true if both active and credentials are available for Android" do
|
||||
expect(consumer_app_android.operational?).to be(true)
|
||||
end
|
||||
|
||||
it "returns true if both active and credentials are available for iOS" do
|
||||
expect(consumer_app_ios.operational?).to be(true)
|
||||
end
|
||||
end
|
||||
|
||||
context "with forem apps" do
|
||||
it "returns true/false based on the ENV variable for the forem apps" do
|
||||
context "with Forem apps" do
|
||||
it "returns true/false based on the ENV variable for the Forem apps" do
|
||||
forem_consumer_app = ConsumerApps::FindOrCreateByQuery.call(
|
||||
app_bundle: ConsumerApp::FOREM_BUNDLE,
|
||||
platform: ConsumerApp::FOREM_APP_PLATFORMS.sample,
|
||||
)
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return("asdf123")
|
||||
expect(forem_consumer_app.operational?).to be true
|
||||
expect(forem_consumer_app.operational?).to be(true)
|
||||
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return(nil)
|
||||
expect(forem_consumer_app.operational?).to be false
|
||||
expect(forem_consumer_app.operational?).to be(false)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "after an update" do
|
||||
it "recreates the Redis-backed Rpush app" do
|
||||
it "recreates the Rpush app for Android", :aggregate_failures do
|
||||
mock_rpush(consumer_app_android)
|
||||
|
||||
rpush_app = ConsumerApps::RpushAppQuery.call(
|
||||
app_bundle: consumer_app.app_bundle,
|
||||
platform: consumer_app.platform,
|
||||
app_bundle: consumer_app_android.app_bundle,
|
||||
platform: consumer_app_android.platform,
|
||||
)
|
||||
auth_key = rpush_app.certificate
|
||||
|
||||
# The Redis-backed Rpush App has the ConsumerApp's auth_credentials
|
||||
expect(rpush_app).to be_instance_of(Rpush::Apns2::App)
|
||||
expect(auth_key).to eq(consumer_app.auth_credentials)
|
||||
# The Rpush App has the ConsumerApp's auth_credentials
|
||||
expect(rpush_app).to be_instance_of(Rpush::Gcm::App)
|
||||
expect(rpush_app.auth_key).to eq(consumer_app_android.auth_credentials)
|
||||
|
||||
consumer_app.auth_key = "new_auth_key"
|
||||
expect(consumer_app.save).to be true
|
||||
consumer_app_android.auth_key = "new_auth_key"
|
||||
expect(consumer_app_android.save).to be(true)
|
||||
|
||||
# After update fetch again
|
||||
rpush_app = ConsumerApps::RpushAppQuery.call(
|
||||
app_bundle: consumer_app.app_bundle,
|
||||
platform: consumer_app.platform,
|
||||
app_bundle: consumer_app_android.app_bundle,
|
||||
platform: consumer_app_android.platform,
|
||||
)
|
||||
|
||||
# The Redis-backed Rpush App has the new ConsumerApp's auth_credentials
|
||||
# The Rpush App has the new ConsumerApp's auth_credentials
|
||||
expect(rpush_app).to be_instance_of(Rpush::Gcm::App)
|
||||
expect(rpush_app.auth_key).to eq("new_auth_key")
|
||||
end
|
||||
|
||||
it "recreates the Rpush app for iOS", :aggregate_failures do
|
||||
mock_rpush(consumer_app_ios)
|
||||
|
||||
rpush_app = ConsumerApps::RpushAppQuery.call(
|
||||
app_bundle: consumer_app_ios.app_bundle,
|
||||
platform: consumer_app_ios.platform,
|
||||
)
|
||||
|
||||
# The Rpush App has the ConsumerApp's auth_credentials
|
||||
expect(rpush_app).to be_instance_of(Rpush::Apns2::App)
|
||||
expect(rpush_app.certificate).to eq(consumer_app_ios.auth_credentials)
|
||||
|
||||
consumer_app_ios.auth_key = "new_auth_key"
|
||||
expect(consumer_app_ios.save).to be(true)
|
||||
|
||||
# After update fetch again
|
||||
rpush_app = ConsumerApps::RpushAppQuery.call(
|
||||
app_bundle: consumer_app_ios.app_bundle,
|
||||
platform: consumer_app_ios.platform,
|
||||
)
|
||||
|
||||
# The Rpush App has the new ConsumerApp's auth_credentials
|
||||
expect(rpush_app).to be_instance_of(Rpush::Apns2::App)
|
||||
expect(rpush_app.certificate).to eq("new_auth_key")
|
||||
end
|
||||
|
|
|
|||
17
spec/models/device_spec.rb
Normal file
17
spec/models/device_spec.rb
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
require "rails_helper"
|
||||
|
||||
RSpec.describe Device, type: :model do
|
||||
let(:device) { create(:device) }
|
||||
|
||||
describe "validations" do
|
||||
subject { device }
|
||||
|
||||
describe "builtin validations" do
|
||||
it { is_expected.to belong_to(:consumer_app) }
|
||||
it { is_expected.to belong_to(:user) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:token) }
|
||||
it { is_expected.to validate_uniqueness_of(:token).scoped_to(%i[user_id platform consumer_app_id]) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -8,7 +8,7 @@ RSpec.describe ConsumerApps::FindOrCreateByQuery, type: :query do
|
|||
expect do
|
||||
app = described_class.call(
|
||||
app_bundle: ConsumerApp::FOREM_BUNDLE,
|
||||
platform: Device::IOS,
|
||||
platform: :ios,
|
||||
)
|
||||
expect(app).to be_instance_of(ConsumerApp)
|
||||
end.to change(ConsumerApp, :count).by(1)
|
||||
|
|
|
|||
|
|
@ -2,11 +2,13 @@ require "rails_helper"
|
|||
|
||||
RSpec.describe ConsumerApps::RpushAppQuery, type: :query do
|
||||
let(:consumer_app) do
|
||||
ConsumerApps::FindOrCreateByQuery.call(app_bundle: ConsumerApp::FOREM_BUNDLE, platform: Device::IOS)
|
||||
ConsumerApps::FindOrCreateByQuery.call(app_bundle: ConsumerApp::FOREM_BUNDLE, platform: :ios)
|
||||
end
|
||||
|
||||
describe "Redis-backed rpush app" do
|
||||
describe "Rpush app" do
|
||||
it "is recreated after updating a ConsumerApp" do
|
||||
mock_rpush(consumer_app)
|
||||
|
||||
# Fetch rpush app associated to the target
|
||||
rpush_app = described_class.call(
|
||||
app_bundle: consumer_app.app_bundle,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ abort("The Rails environment is running in production mode!") if Rails.env.produ
|
|||
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
|
||||
require "fakeredis/rspec"
|
||||
require "pundit/matchers"
|
||||
require "pundit/rspec"
|
||||
require "webmock/rspec"
|
||||
|
|
@ -72,6 +73,7 @@ RSpec.configure do |config|
|
|||
config.include Devise::Test::IntegrationHelpers, type: :request
|
||||
config.include FactoryBot::Syntax::Methods
|
||||
config.include OmniauthHelpers
|
||||
config.include RpushHelpers
|
||||
config.include SidekiqTestHelpers
|
||||
|
||||
config.after(:each, type: :system) do
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
require "rails_helper"
|
||||
require "requests/shared_examples/internal_policy_dependant_request"
|
||||
|
||||
RSpec.describe "/admin/consumer_apps", type: :request do
|
||||
RSpec.describe "Admin - Consumer Apps", type: :request do
|
||||
let(:get_resource) { get admin_consumer_apps_path }
|
||||
let(:params) do
|
||||
{
|
||||
app_bundle: Faker::Internet.domain_name(subdomain: true),
|
||||
platform: Device::IOS,
|
||||
platform: "ios",
|
||||
auth_key: "sample_data"
|
||||
}
|
||||
end
|
||||
|
|
@ -55,11 +55,15 @@ RSpec.describe "/admin/consumer_apps", type: :request do
|
|||
end
|
||||
|
||||
describe "PUT /admin/consumer_apps" do
|
||||
let!(:consumer_app) { create(:consumer_app, app_bundle: "com.bundle.test") }
|
||||
let(:consumer_app) { create(:consumer_app, app_bundle: "com.bundle.test") }
|
||||
|
||||
it "updates the ConsumerApp" do
|
||||
mock_rpush(consumer_app)
|
||||
|
||||
put admin_consumer_app_path(consumer_app.id), params: params
|
||||
|
||||
consumer_app.reload
|
||||
|
||||
expect(consumer_app.app_bundle).to eq(params[:app_bundle])
|
||||
expect(consumer_app.platform).to eq(params[:platform])
|
||||
expect(consumer_app.auth_key).to eq(params[:auth_key])
|
||||
|
|
@ -106,11 +110,15 @@ RSpec.describe "/admin/consumer_apps", type: :request do
|
|||
end
|
||||
|
||||
describe "PUT /admin/consumer_apps" do
|
||||
let!(:consumer_app) { create(:consumer_app, app_bundle: "com.bundle.test") }
|
||||
let(:consumer_app) { create(:consumer_app, app_bundle: "com.bundle.test") }
|
||||
|
||||
it "updates the ConsumerApp" do
|
||||
mock_rpush(consumer_app)
|
||||
|
||||
put admin_consumer_app_path(consumer_app.id), params: params
|
||||
|
||||
consumer_app.reload
|
||||
|
||||
expect(consumer_app.app_bundle).to eq(params[:app_bundle])
|
||||
expect(consumer_app.platform).to eq(params[:platform])
|
||||
expect(consumer_app.auth_key).to eq(params[:auth_key])
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ RSpec.describe "Devices", type: :request do
|
|||
describe "POST /users/devices" do
|
||||
context "when device persisted" do
|
||||
it "increases device count" do
|
||||
post "/users/devices", params: {
|
||||
post devices_path, params: {
|
||||
token: "123",
|
||||
platform: "Android",
|
||||
app_bundle: consumer_app.app_bundle
|
||||
|
|
@ -31,13 +31,14 @@ RSpec.describe "Devices", type: :request do
|
|||
end
|
||||
|
||||
it "does not increase device count" do
|
||||
post "/users/devices", params: params
|
||||
post devices_path, params: params
|
||||
expect(user.devices.count).to eq(0)
|
||||
end
|
||||
|
||||
it "returns an error" do
|
||||
post "/users/devices", params: params
|
||||
expect(response.status).to eq(400)
|
||||
post devices_path, params: params
|
||||
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
expect(response.body).to include("error")
|
||||
end
|
||||
end
|
||||
|
|
@ -55,14 +56,16 @@ RSpec.describe "Devices", type: :request do
|
|||
end
|
||||
|
||||
it "returns an error" do
|
||||
delete "/users/devices/123"
|
||||
delete device_path(123)
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.parsed_body["error"]).to eq("Not Found")
|
||||
expect(response.parsed_body["status"]).to eq(404)
|
||||
end
|
||||
|
||||
it "return an error if device id doesn't match params" do
|
||||
delete "/users/devices/123", params: incomplete_params
|
||||
delete device_path(123), params: incomplete_params
|
||||
|
||||
expect(response.status).to eq(404)
|
||||
expect(response.parsed_body["error"]).to eq("Not Found")
|
||||
expect(response.parsed_body["status"]).to eq(404)
|
||||
|
|
@ -79,7 +82,8 @@ RSpec.describe "Devices", type: :request do
|
|||
end
|
||||
|
||||
it "deletes the device" do
|
||||
delete "/users/devices/#{device.user.id}", params: params
|
||||
delete device_path(device.user.id), params: params
|
||||
|
||||
expect(user.devices.count).to eq(0)
|
||||
expect(response.status).to eq(204)
|
||||
expect(Device.find_by(id: device.id)).to be_nil
|
||||
|
|
|
|||
|
|
@ -21,14 +21,10 @@ RSpec.describe PushNotifications::Send, type: :service do
|
|||
end
|
||||
|
||||
context "with no devices for user" do
|
||||
before do
|
||||
user.devices.delete
|
||||
end
|
||||
|
||||
it "does nothing", :aggregate_failures do
|
||||
expect(user.devices.count).to eq(0)
|
||||
expect { described_class.call(**params) }
|
||||
.not_to change { Rpush::Client::Redis::Notification.all.count }
|
||||
described_class.call(**params)
|
||||
|
||||
sidekiq_assert_no_enqueued_jobs(only: PushNotifications::DeliverWorker)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -36,44 +32,63 @@ RSpec.describe PushNotifications::Send, type: :service do
|
|||
before do
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return("dGVzdGluZw==")
|
||||
allow(ApplicationConfig).to receive(:[]).with("COMMUNITY_NAME").and_return("Forem")
|
||||
create(:device, user: user)
|
||||
end
|
||||
|
||||
it "creates a notification and enqueues it" do
|
||||
expect { described_class.call(**params) }
|
||||
.to change { Rpush::Client::Redis::Notification.all.count }.by(1)
|
||||
.and change(PushNotifications::DeliverWorker.jobs, :size).by(1)
|
||||
device = create(:device, user: user)
|
||||
mocked_objects = mock_rpush(device.consumer_app)
|
||||
|
||||
described_class.call(**params)
|
||||
|
||||
expect(mocked_objects[:rpush_notification]).to have_received(:save!).once
|
||||
|
||||
sidekiq_assert_enqueued_jobs(1, only: PushNotifications::DeliverWorker)
|
||||
end
|
||||
|
||||
it "creates a single notification for each of the user's devices when they have multiple" do
|
||||
create(:device, user: user)
|
||||
consumer_app = create(:consumer_app)
|
||||
devices = create_list(:device, 2, user: user, consumer_app: consumer_app)
|
||||
mocked_objects = mock_rpush(consumer_app)
|
||||
|
||||
expect { described_class.call(**params) }
|
||||
.to change { Rpush::Client::Redis::Notification.all.count }.by(2)
|
||||
.and change(PushNotifications::DeliverWorker.jobs, :size).by(1)
|
||||
described_class.call(**params)
|
||||
|
||||
expect(mocked_objects[:rpush_notification]).to have_received(:save!).exactly(devices.size).times
|
||||
|
||||
sidekiq_assert_enqueued_jobs(1, only: PushNotifications::DeliverWorker)
|
||||
end
|
||||
end
|
||||
|
||||
context "with devices for multiple users" do
|
||||
let(:consumer_app) { create(:consumer_app) }
|
||||
|
||||
before do
|
||||
allow(ApplicationConfig).to receive(:[]).with("RPUSH_IOS_PEM").and_return("dGVzdGluZw==")
|
||||
allow(ApplicationConfig).to receive(:[]).with("COMMUNITY_NAME").and_return("Forem")
|
||||
create(:device, user: user)
|
||||
create(:device, user: user2)
|
||||
|
||||
create(:device, user: user, consumer_app: consumer_app)
|
||||
create(:device, user: user2, consumer_app: consumer_app)
|
||||
end
|
||||
|
||||
it "creates a notification and enqueues it" do
|
||||
expect { described_class.call(**many_targets_params) }
|
||||
.to change { Rpush::Client::Redis::Notification.all.count }.by(2)
|
||||
.and change { PushNotifications::DeliverWorker.jobs.size }.by(1)
|
||||
mocked_objects = mock_rpush(consumer_app)
|
||||
|
||||
described_class.call(**many_targets_params)
|
||||
|
||||
expect(mocked_objects[:rpush_notification]).to have_received(:save!).exactly(2).times
|
||||
|
||||
sidekiq_assert_enqueued_jobs(1, only: PushNotifications::DeliverWorker)
|
||||
end
|
||||
|
||||
it "creates a single notification for each of the user's devices when they have multiple" do
|
||||
create(:device, user: user)
|
||||
|
||||
expect { described_class.call(**many_targets_params) }
|
||||
.to change { Rpush::Client::Redis::Notification.all.count }.by(3)
|
||||
.and change { PushNotifications::DeliverWorker.jobs.size }.by(1)
|
||||
mocked_objects = mock_rpush(consumer_app)
|
||||
|
||||
described_class.call(**many_targets_params)
|
||||
|
||||
expect(mocked_objects[:rpush_notification]).to have_received(:save!).exactly(3).times
|
||||
|
||||
sidekiq_assert_enqueued_jobs(1, only: PushNotifications::DeliverWorker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
37
spec/support/rpush_helpers.rb
Normal file
37
spec/support/rpush_helpers.rb
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
module RpushHelpers
|
||||
def mock_rpush(consumer_app)
|
||||
if consumer_app.android?
|
||||
rpush_class = Rpush::Gcm::App
|
||||
rpush_notification_class = Rpush::Gcm::Notification
|
||||
auth_key = :auth_key
|
||||
elsif consumer_app.ios?
|
||||
rpush_class = Rpush::Apns2::App
|
||||
rpush_notification_class = Rpush::Apns2::Notification
|
||||
auth_key = :certificate
|
||||
end
|
||||
|
||||
rpush_app = rpush_class.new(
|
||||
:name => consumer_app.app_bundle,
|
||||
auth_key => consumer_app.auth_credentials,
|
||||
)
|
||||
allow(rpush_app).to receive(:destroy)
|
||||
|
||||
rpush_notification = rpush_notification_class.new
|
||||
allow(rpush_notification).to receive(:save!)
|
||||
allow(rpush_notification_class).to receive(:new).and_return(rpush_notification)
|
||||
|
||||
relation = double
|
||||
allow(relation).to receive(:first).and_return(rpush_app)
|
||||
allow(rpush_class).to receive(:where).and_return(relation)
|
||||
|
||||
original = consumer_app.method(:save)
|
||||
allow(consumer_app).to receive(:save) do
|
||||
rpush_app.public_send("#{auth_key}=", consumer_app.auth_credentials)
|
||||
original.call
|
||||
end
|
||||
|
||||
{
|
||||
rpush_notification: rpush_notification
|
||||
}
|
||||
end
|
||||
end
|
||||
BIN
vendor/cache/fakeredis-0.8.0.gem
vendored
Normal file
BIN
vendor/cache/fakeredis-0.8.0.gem
vendored
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue