diff --git a/app/assets/stylesheets/_main-nav.scss b/app/assets/stylesheets/_main-nav.scss
index fba77fc3..62deb412 100644
--- a/app/assets/stylesheets/_main-nav.scss
+++ b/app/assets/stylesheets/_main-nav.scss
@@ -5,7 +5,7 @@
align-items: center;
padding: 1rem 5rem;
- @media(min-width: 80rem) {
+ @media (min-width: 80rem) {
justify-content: space-between;
}
@@ -17,7 +17,7 @@
margin-right: auto;
}
- @media(min-width: 80rem) {
+ @media (min-width: 80rem) {
display: flex;
}
}
@@ -27,10 +27,9 @@
li {
margin: 0 2rem;
- font-weight: bold;
}
- @media(min-width: 80rem) {
+ @media (min-width: 80rem) {
display: flex;
}
}
@@ -49,7 +48,7 @@
display: none;
padding: 1rem 3rem;
- @media(min-width: 80rem) {
+ @media (min-width: 80rem) {
display: flex;
}
}
diff --git a/app/controllers/plan_changes_controller.rb b/app/controllers/plan_changes_controller.rb
index 1875b067..cdcc1d0e 100644
--- a/app/controllers/plan_changes_controller.rb
+++ b/app/controllers/plan_changes_controller.rb
@@ -9,25 +9,30 @@ def index
redirect_to root_path unless current_user&.active_subscription
@user = current_user
+ @pending_plan_change = @user.pending_plan_change
@plans = Plan.all
end
- # GET /users/:user_id/plan_changes/new
- # GET /users/:user_id/plan_changes/new.json
- def new
- @plan_change = UserPlanChange.new
- end
-
# POST /users/:user_id/plan_changes
# POST /users/:user_id/plan_changes.json
def create
- @plan = UserPlanChange.new(user_id: current_user.id, old_plan_id: plan_change_params[:old_plan_id], new_plan_id: plan_change_params[:new_plan_id], status: 'pending')
+ @plan_change =
+ current_user.user_plan_changes.new(
+ old_plan_id: plan_change_params[:old_plan_id],
+ new_plan_id: plan_change_params[:new_plan_id],
+ status: 'pending'
+ )
respond_to do |format|
- if @plan.save
- format.json { render json: @plan, status: :ok }
+ if @plan_change.save
+ format.html { render :index, notice: 'Plan changed successfully' }
+ format.json { render json: @plan_change, status: :ok }
else
- format.html { render :index }
+ format.html { render :index, notice: 'Error while changing plans' }
+ format.json do
+ render json: { errors: @plan_change.errors.full_messages },
+ status: :ok
+ end
end
end
end
diff --git a/app/javascript/bundles/User/components/CurrentPlan.jsx b/app/javascript/bundles/User/components/CurrentPlan.jsx
index d11b8459..892abd8f 100644
--- a/app/javascript/bundles/User/components/CurrentPlan.jsx
+++ b/app/javascript/bundles/User/components/CurrentPlan.jsx
@@ -18,16 +18,18 @@ const useStyles = makeStyles(theme => ({
const CHANGE_PLAN_ENDPOINT = id => `/users/${id}/plan_changes`
-function CurrentPlanView ({ user, activePlan, plans }) {
+function CurrentPlanView ({ user, currentPlan, pendingPlanChange, plans }) {
const classes = useStyles()
- const [changedPlan, setPlanChanged] = useState(false)
+ const [planChange, setPlanChange] = useState(pendingPlanChange)
+ const pendingPlanChangePlan =
+ planChange && plans.find(plan => plan.id == planChange.new_plan_id)
const changePlan = async selectedPlanId => {
try {
- await fetch(CHANGE_PLAN_ENDPOINT(user.id), {
+ const response = await fetch(CHANGE_PLAN_ENDPOINT(user.id), {
body: JSON.stringify({
plan_change: {
- old_plan_id: activePlan.id,
+ old_plan_id: currentPlan.id,
new_plan_id: selectedPlanId
}
}),
@@ -38,9 +40,12 @@ function CurrentPlanView ({ user, activePlan, plans }) {
},
method: 'post'
})
- setPlanChanged(true)
+
+ const json = await response.json()
+ setPlanChange(json)
} catch (error) {
- console.error(error) // TODO: Replace with sentry
+ console.error(error)
+ Sentry.captureException(error)
}
}
@@ -49,16 +54,17 @@ function CurrentPlanView ({ user, activePlan, plans }) {
Change your susbcription
- The plan you're using to contribute is{' '}
- {activePlan.name} .
+ Your membership tier is {currentPlan.name} .
-
Available plans
- You can change your current plan for another one at anytime.
- {changedPlan && (
+ Available tiers
+ You can change your membership tier to another one at anytime.
+ {planChange && (
- We have changed your subscription successfully.
+ Your plan is scheduled to change to{' '}
+ {pendingPlanChangePlan.name} . The new amount will be
+ charged on your next billing cycle.
)}
{plans.map(plan => {
@@ -78,10 +84,10 @@ function CurrentPlanView ({ user, activePlan, plans }) {
- Pick plan
+ Select this tier
)
diff --git a/app/javascript/bundles/User/components/DonationsHistory.jsx b/app/javascript/bundles/User/components/DonationsHistory.jsx
index c80d4c7f..a66d321f 100644
--- a/app/javascript/bundles/User/components/DonationsHistory.jsx
+++ b/app/javascript/bundles/User/components/DonationsHistory.jsx
@@ -34,7 +34,7 @@ const calcDonationTotal = donations => {
return donations.reduce((acc, donation) => (acc += donation), 0)
}
-function DonationsView ({ activePlan, donations }) {
+function DonationsView ({ currentPlan, donations }) {
const classes = useStyles()
const donatedAmount = calcDonationTotal(
donations.map(donation => Number(donation.amount))
diff --git a/app/javascript/bundles/User/components/SubscriptionCancel.jsx b/app/javascript/bundles/User/components/SubscriptionCancel.jsx
index 8b921003..8c6aefc3 100644
--- a/app/javascript/bundles/User/components/SubscriptionCancel.jsx
+++ b/app/javascript/bundles/User/components/SubscriptionCancel.jsx
@@ -48,7 +48,7 @@ const NoSubscriptionView = ({ user }) => {
function SubscriptionCancelView ({
user,
subscription,
- activePlan,
+ currentPlan,
isSubscriptionChanging
}) {
const classes = useStyles()
@@ -95,8 +95,7 @@ function SubscriptionCancelView ({
You're subscribed
- The plan you're using to contribute is{' '}
- {activePlan.name} .
+ Your membership tier is {currentPlan.name} .
{!isSubscriptionChanging && (
Do you want to terminate your current subscription?
-
-
- Terminating your subscription will stop your current plan and the
- benefits you receive from it.
-
-
Close
diff --git a/app/jobs/find_overdue_subscriptions_job.rb b/app/jobs/find_overdue_subscriptions_job.rb
index c308f367..ab7f5aca 100644
--- a/app/jobs/find_overdue_subscriptions_job.rb
+++ b/app/jobs/find_overdue_subscriptions_job.rb
@@ -4,11 +4,11 @@ class FindOverdueSubscriptionsJob < ApplicationJob
queue_as :default
def perform
- subscriptions_to_charge = Subscription.where(active: true).select do |subscription|
- # create charge if there's no last_charge_at date, meaning it would be the
- # first time we attempt to charge this customer this subscription.
- subscription.last_charge_at ? subscription.last_charge_at <= 30.days.ago : true
- end
+ subscriptions_to_charge =
+ Subscription.where(active: true).where(
+ 'last_charge_at IS NULL OR last_charge_at <= ?',
+ 30.days.ago
+ )
subscriptions_to_charge.each do |subscription_to_charge|
SubscriptionPaymentJob.perform_later(subscription_to_charge)
diff --git a/app/jobs/toggle_user_active_subscription_plan_job.rb b/app/jobs/toggle_user_active_subscription_plan_job.rb
index d5be5450..24ac690a 100644
--- a/app/jobs/toggle_user_active_subscription_plan_job.rb
+++ b/app/jobs/toggle_user_active_subscription_plan_job.rb
@@ -4,24 +4,31 @@ class ToggleUserActiveSubscriptionPlanJob < ApplicationJob
queue_as :default
def perform(user_plan_change)
- # find the user
+ # find user
user = User.find(user_plan_change.user_id)
# disable current user subscription.
old_subscription = user.active_subscription
+
ActiveRecord::Base.transaction do
old_subscription.update(active: false)
# creates a new subscription with the new plan details.
- new_subscription = Subscription.new(
- user_id: user_plan_change.user_id,
- plan_id: user_plan_change.new_plan_id,
- last_charge_at: old_subscription.last_charge_at,
- active: true
- )
+ new_subscription =
+ Subscription.new(
+ user_id: user.id,
+ plan_id: user_plan_change.new_plan_id,
+ last_charge_at: old_subscription.last_charge_at,
+ active: true
+ )
if new_subscription.save
- UserPlanChange.find(user_plan_change.id).update(status: "succeeded")
+ UserPlanChange.find(user_plan_change.id).update(status: 'succeeded')
+ else
+ Raven.capture_message(
+ "We couldn't process subscription plan change",
+ extra: { user_id: user.id, subscription_id: old_subscription.id }
+ )
end
end
end
diff --git a/app/models/subscription.rb b/app/models/subscription.rb
index 340cbe4d..164f0272 100644
--- a/app/models/subscription.rb
+++ b/app/models/subscription.rb
@@ -15,9 +15,8 @@
#
# Indexes
#
-# index_subscriptions_on_plan_id (plan_id)
-# index_subscriptions_on_user_id (user_id)
-# index_subscriptions_on_user_id_and_plan_id_and_active (user_id,plan_id,active) UNIQUE
+# index_subscriptions_on_plan_id (plan_id)
+# index_subscriptions_on_user_id (user_id)
#
class Subscription < ApplicationRecord
before_create :store_start_date
@@ -25,8 +24,7 @@ class Subscription < ApplicationRecord
belongs_to :user, optional: true
belongs_to :plan
- validates :plan_id, presence: true
- validates :user_id, uniqueness: {scope: %i[plan_id active]}, if: :user?
+ validate :only_one_active_subscription, on: :create
def user?
!user_id.blank?
@@ -43,4 +41,10 @@ def cancel!
def store_start_date
self.start_date = DateTime.now if start_date.nil?
end
+
+ def only_one_active_subscription
+ if Subscription.exists?(user_id: user_id, active: true)
+ errors.add(:base, 'already has an active subscription')
+ end
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 6091700b..66a3e99a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -32,6 +32,7 @@ class User < ApplicationRecord
has_many :subscriptions
has_many :donations
+ has_many :user_plan_changes
validates :external_id, presence: true
@@ -56,16 +57,26 @@ def admin?
end
def current_streak
- return nil unless active_subscription
+ subscription = active_subscription
- start_date ||= active_subscription.start_date
+ return nil unless subscription
- return 1 if (Date.today - start_date.to_date).zero? # first month of subscription
+ start_date = subscription.start_date
+ today = Date.today
- ((Date.today - start_date.to_date).to_f / 365 * 12).round
+ months =
+ (today.year * 12 + today.month) -
+ (start_date.year * 12 + start_date.month)
+ months += 1 if months == 0
+
+ months
+ end
+
+ def pending_plan_change
+ user_plan_changes.where(status: :pending).first
end
def active_subscription
- subscriptions.eager_load(:plan).where(active: true).first
+ subscriptions.includes(:plan).where(active: true).last
end
end
diff --git a/app/models/user_plan_change.rb b/app/models/user_plan_change.rb
index a5af0bea..842dbb72 100644
--- a/app/models/user_plan_change.rb
+++ b/app/models/user_plan_change.rb
@@ -15,8 +15,16 @@
class UserPlanChange < ApplicationRecord
belongs_to :user
- enum status: {succeeded: 0, pending: 1, failed: 2}
+ enum status: { succeeded: 0, pending: 1, failed: 2 }
validates :old_plan_id, :new_plan_id, :user_id, presence: true
- validates :user_id, uniqueness: true
+ validate :only_one_pending_plan_change, on: :create
+
+ private
+
+ def only_one_pending_plan_change
+ if UserPlanChange.exists?(user_id: user_id, status: 'pending')
+ errors.add(:base, 'already has a pending plan change')
+ end
+ end
end
diff --git a/app/views/layouts/admin.html.erb b/app/views/layouts/admin.html.erb
index d6cf527d..c327e787 100644
--- a/app/views/layouts/admin.html.erb
+++ b/app/views/layouts/admin.html.erb
@@ -8,7 +8,7 @@
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
-
+
<%= render 'layouts/sentry' %>
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
index 5b872f77..a49eeaa0 100644
--- a/app/views/layouts/application.html.erb
+++ b/app/views/layouts/application.html.erb
@@ -7,14 +7,8 @@
<%= csp_meta_tag %>
<%= content_for?(:head) ? yield(:head) : '' %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
-
-
+
+
diff --git a/app/views/layouts/backoffice.html.erb b/app/views/layouts/backoffice.html.erb
index 8e16f4b4..1a5ec35c 100644
--- a/app/views/layouts/backoffice.html.erb
+++ b/app/views/layouts/backoffice.html.erb
@@ -9,7 +9,7 @@
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
-
+
diff --git a/app/views/plan_changes/index.html.erb b/app/views/plan_changes/index.html.erb
index 6ef756f0..12b92252 100644
--- a/app/views/plan_changes/index.html.erb
+++ b/app/views/plan_changes/index.html.erb
@@ -1,3 +1,3 @@
- <%= react_component("CurrentPlan", props: {user: @user, activePlan: @user.active_subscription&.plan, plans: @plans}) %>
+ <%= react_component("CurrentPlan", props: {user: @user, currentPlan: @user.active_subscription&.plan, pendingPlanChange: @pending_plan_change, plans: @plans}) %>
diff --git a/app/views/users/subscription.html.erb b/app/views/users/subscription.html.erb
index e6a016b2..50a7b31d 100644
--- a/app/views/users/subscription.html.erb
+++ b/app/views/users/subscription.html.erb
@@ -4,7 +4,7 @@
<%= react_component("SubscriptionCancel", props: {
user: @user,
subscription: @subscription,
- activePlan: @subscription&.plan,
+ currentPlan: @subscription&.plan,
isSubscriptionChanging: @is_subscription_changing
}) %>
diff --git a/codecov.yml b/codecov.yml
index 1b446323..0155d936 100644
--- a/codecov.yml
+++ b/codecov.yml
@@ -1,6 +1,10 @@
coverage:
status:
+ patch:
+ default:
+ target: auto
+ threshold: 3%
project:
default:
target: auto
- threshold: 2%
+ threshold: 3%
diff --git a/config/routes.rb b/config/routes.rb
index a2ec8ab7..c32919bc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -1,21 +1,21 @@
# frozen_string_literal: true
-require "sidekiq/web"
-require "sidekiq-scheduler/web"
+require 'sidekiq/web'
+require 'sidekiq-scheduler/web'
Rails.application.routes.draw do
- get "/card/new" => "billings#new_card", :as => :add_payment_method
- post "/card" => "billings#create_card", :as => :create_payment_method
+ get '/card/new' => 'billings#new_card', as: :add_payment_method
+ post '/card' => 'billings#create_card', as: :create_payment_method
- get "/thank-you" => "static_pages#thank_you"
+ get '/thank-you' => 'static_pages#thank_you'
resources :subscription_charges, only: %i[new edit create update]
resources :billings, only: %i[new create]
resources :charges,
- only: %i[new create], path: "donate", path_names: {new: ""}
+ only: %i[new create], path: 'donate', path_names: { new: '' }
namespace :admin do
- get "/dashboard" => "dashboard#index"
+ get '/dashboard' => 'dashboard#index'
resources :users
resources :plans
resources :funds, except: %i[show]
@@ -23,25 +23,25 @@
resources :donations, only: %i[index show]
end
- get "/admin", to: redirect("/admin/dashboard")
+ get '/admin', to: redirect('/admin/dashboard')
resources :users, only: %i[show new edit update create destroy] do
- get "/subscription" => "users#subscription", :as => :current_subscription
- get "/donations" => "users#donation_history", :as => :latest_donations
+ get '/subscription' => 'users#subscription', as: :current_subscription
+ get '/donations' => 'users#donation_history', as: :latest_donations
resource :streak, only: %i[show]
resource :subscription, only: %i[destroy]
- resources :plan_changes, only: %i[index new create]
+ resources :plan_changes, only: %i[index create]
end
- get "/login" => "sessions#login"
- get "/signup" => "sessions#signup"
+ get '/login' => 'sessions#login'
+ get '/signup' => 'sessions#signup'
if Rails.env.production?
- mount Sidekiq::Web => "/sidekiq",
- :constraints => AdminConstraint.new(require_master: true)
+ mount Sidekiq::Web => '/sidekiq',
+ constraints: AdminConstraint.new(require_master: true)
else
- mount Sidekiq::Web => "/sidekiq"
+ mount Sidekiq::Web => '/sidekiq'
end
- root "static_pages#home"
+ root 'static_pages#home'
end
diff --git a/config/sidekiq.yml b/config/sidekiq.yml
index 190fb0ce..83ad733b 100644
--- a/config/sidekiq.yml
+++ b/config/sidekiq.yml
@@ -5,12 +5,12 @@
- mailers
:schedule:
find_overdue_subscriptions:
- every: '45m'
+ every: '1h'
queue: default
class: FindOverdueSubscriptionsJob
description: 'This job finds all active overdue subscriptions and creates a sidekiq job to charge the subscriber'
find_subscription_plan_changes:
- every: '45m'
+ every: '15m'
queue: default
class: ChangeSubscriptionPlansJob
description: 'This job finds all pending subscription changes and creates a sidekiq job to perform the plan change'
diff --git a/db/migrate/20200914231027_remove_subscription_index.rb b/db/migrate/20200914231027_remove_subscription_index.rb
new file mode 100644
index 00000000..e503b106
--- /dev/null
+++ b/db/migrate/20200914231027_remove_subscription_index.rb
@@ -0,0 +1,5 @@
+class RemoveSubscriptionIndex < ActiveRecord::Migration[6.0]
+ def change
+ remove_index :subscriptions, [:user_id, :plan_id, :active]
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 4e0bc4b5..2b6e341f 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_09_01_215441) do
+ActiveRecord::Schema.define(version: 2020_09_14_231027) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -102,7 +102,6 @@
t.datetime "start_date"
t.datetime "last_charge_at"
t.index ["plan_id"], name: "index_subscriptions_on_plan_id"
- t.index ["user_id", "plan_id", "active"], name: "index_subscriptions_on_user_id_and_plan_id_and_active", unique: true
t.index ["user_id"], name: "index_subscriptions_on_user_id"
end
diff --git a/renovate.json b/renovate.json
index 0f49eb57..c36e8039 100644
--- a/renovate.json
+++ b/renovate.json
@@ -1,6 +1,6 @@
{
"extends": ["config:base"],
- "schedule": ["before 11pm on Monday"],
+ "schedule": ["before 4am on the first day of the month"],
"bundler": {
"enabled": true
}
diff --git a/spec/factories/subscriptions.rb b/spec/factories/subscriptions.rb
index e2aa75d1..68be67ab 100644
--- a/spec/factories/subscriptions.rb
+++ b/spec/factories/subscriptions.rb
@@ -15,9 +15,8 @@
#
# Indexes
#
-# index_subscriptions_on_plan_id (plan_id)
-# index_subscriptions_on_user_id (user_id)
-# index_subscriptions_on_user_id_and_plan_id_and_active (user_id,plan_id,active) UNIQUE
+# index_subscriptions_on_plan_id (plan_id)
+# index_subscriptions_on_user_id (user_id)
#
FactoryBot.define do
factory :subscription do
diff --git a/spec/features/plan_change_spec.rb b/spec/features/plan_change_spec.rb
index 6924f08c..e0102235 100644
--- a/spec/features/plan_change_spec.rb
+++ b/spec/features/plan_change_spec.rb
@@ -23,7 +23,7 @@
click_link 'Change Subscription'
- expect(page).to have_content('Available plans')
+ expect(page).to have_content('Available tiers')
within "#plan-#{active_plan.id}" do
expect(page).to have_content(active_plan.name)
@@ -31,10 +31,10 @@
within "#plan-#{new_plan.id}" do
expect(page).to have_content(new_plan.name)
- click_button 'Pick plan'
+ click_button 'Select this tier'
end
- expect(page).to have_content('We have changed your subscription successfully.')
+ expect(page).to have_content("Your plan is scheduled to change to #{new_plan.name}")
expect(UserPlanChange.where(user_id: user.id).length).to be(1)
end
end
diff --git a/spec/jobs/find_overdue_subscriptions_job_spec.rb b/spec/jobs/find_overdue_subscriptions_job_spec.rb
index 5e947c3a..0e9f9379 100644
--- a/spec/jobs/find_overdue_subscriptions_job_spec.rb
+++ b/spec/jobs/find_overdue_subscriptions_job_spec.rb
@@ -6,12 +6,10 @@
let!(:recently_paid_active_subscription) { FactoryBot.create(:subscription, last_charge_at: 2.weeks.ago) }
let!(:inactive_subscription) { FactoryBot.create(:subscription, active: false) }
let!(:active_subscription_one) { FactoryBot.create(:subscription, last_charge_at: 32.days.ago) }
- let!(:active_subscription_two) { FactoryBot.create(:subscription) }
-
- subject(:job) { described_class.perform_later }
+ let!(:active_subscription_two) { FactoryBot.create(:subscription, last_charge_at: nil) }
it 'queues the job' do
- expect { job }
+ expect { FindOverdueSubscriptionsJob.perform_later }
.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
end
@@ -20,9 +18,8 @@
end
it 'executes perform' do
- expect do
- described_class.perform_now
- end.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(2)
+ expect { FindOverdueSubscriptionsJob.perform_now }
+ .to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(2)
end
after do
diff --git a/spec/jobs/toggle_user_active_subscription_plan_job_spec.rb b/spec/jobs/toggle_user_active_subscription_plan_job_spec.rb
index b2fde652..95916b52 100644
--- a/spec/jobs/toggle_user_active_subscription_plan_job_spec.rb
+++ b/spec/jobs/toggle_user_active_subscription_plan_job_spec.rb
@@ -2,17 +2,16 @@
require "rails_helper"
-RSpec.describe ToggleUserActiveSubscriptionPlanJob do
+RSpec.describe ToggleUserActiveSubscriptionPlanJob, type: :job do
let(:user) { FactoryBot.create(:user) }
let!(:old_plan) { FactoryBot.create(:plan) }
let!(:new_plan) { FactoryBot.create(:plan) }
let!(:active_subscription) { FactoryBot.create(:subscription, user: user, plan: old_plan) }
- let!(:plan_change) { FactoryBot.create(:user_plan_change, user: user, old_plan_id: old_plan.id, new_plan_id: new_plan.id, status: "pending") }
-
- subject(:job) { described_class.perform_later(plan_change) }
it "queues the job" do
- expect { job }
+ plan_change = FactoryBot.create(:user_plan_change, user: user, old_plan_id: old_plan.id, new_plan_id: new_plan.id, status: "pending")
+
+ expect { ToggleUserActiveSubscriptionPlanJob.perform_later(plan_change) }
.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :size).by(1)
end
@@ -21,15 +20,19 @@
end
it "executes perform" do
+ plan_change = FactoryBot.create(:user_plan_change, user: user, old_plan_id: old_plan.id, new_plan_id: new_plan.id, status: "pending")
+
expect(Subscription.count).to eq(1)
expect(user.active_subscription.plan.id).to eq(old_plan.id)
- perform_enqueued_jobs { job }
+ perform_enqueued_jobs { ToggleUserActiveSubscriptionPlanJob.perform_later(plan_change) }
expect(Subscription.count).to eq(2)
user.active_subscription.reload
+ plan_change.reload
+
expect(user.active_subscription.plan.id).to eq(new_plan.id)
- expect(plan_change.reload.status).to eq("succeeded")
+ expect(plan_change.status).to eq("succeeded")
end
after do
diff --git a/spec/models/subscription_spec.rb b/spec/models/subscription_spec.rb
index be6ba15f..e066b7cf 100644
--- a/spec/models/subscription_spec.rb
+++ b/spec/models/subscription_spec.rb
@@ -15,14 +15,14 @@
#
# Indexes
#
-# index_subscriptions_on_plan_id (plan_id)
-# index_subscriptions_on_user_id (user_id)
-# index_subscriptions_on_user_id_and_plan_id_and_active (user_id,plan_id,active) UNIQUE
+# index_subscriptions_on_plan_id (plan_id)
+# index_subscriptions_on_user_id (user_id)
#
require 'rails_helper'
RSpec.describe Subscription, type: :model do
let(:subscription) { FactoryBot.create(:subscription) }
+ let(:plan) { FactoryBot.create(:plan) }
subject { subscription }
@@ -34,7 +34,37 @@
end
describe 'validations' do
- it { is_expected.to validate_presence_of(:plan_id) }
- it { is_expected.to validate_uniqueness_of(:user_id).scoped_to(:plan_id, :active).ignoring_case_sensitivity }
+ it { should belong_to(:plan) }
+ it { should belong_to(:user).optional(true) }
+
+ it 'can have many subscriptions' do
+ user = FactoryBot.create(:user)
+ FactoryBot.create(:subscription, user: user, active: false)
+
+ new_subscription = Subscription.new(
+ user_id: user.id,
+ plan_id: plan.id,
+ active: true
+ )
+
+ new_subscription.save
+
+ expect(new_subscription.errors).to be_empty
+ end
+
+ it 'only can be one active subscription at a time' do
+ user = FactoryBot.create(:user)
+ FactoryBot.create(:subscription, user: user)
+
+ new_subscription = Subscription.new(
+ user_id: user.id,
+ plan_id: plan.id,
+ active: true
+ )
+
+ new_subscription.save
+
+ expect(new_subscription.errors.full_messages).to eq(['already has an active subscription'])
+ end
end
end
diff --git a/spec/models/user_plan_change_spec.rb b/spec/models/user_plan_change_spec.rb
index 8fbaf770..59c6afa8 100644
--- a/spec/models/user_plan_change_spec.rb
+++ b/spec/models/user_plan_change_spec.rb
@@ -15,6 +15,9 @@
require 'rails_helper'
RSpec.describe UserPlanChange, type: :model do
+ let!(:user) { FactoryBot.create(:user) }
+ let!(:plan1) { FactoryBot.create(:plan) }
+ let!(:plan2) { FactoryBot.create(:plan) }
let!(:user_plan_change) { FactoryBot.create(:user_plan_change) }
subject { user_plan_change }
@@ -30,5 +33,22 @@
it { is_expected.to validate_presence_of(:user_id) }
it { is_expected.to validate_presence_of(:old_plan_id) }
it { is_expected.to validate_presence_of(:new_plan_id) }
+
+ it 'can have multiple plan changes' do
+ plan_change = FactoryBot.create(:user_plan_change, user: user, status: 'succeeded', new_plan_id: plan1.id, old_plan_id: plan2.id)
+ second_plan_change = user.user_plan_changes.new(new_plan_id: plan2, old_plan_id: plan1)
+ second_plan_change.save
+
+ expect(second_plan_change.errors).to be_empty
+ end
+
+ it 'allows only one pending plan change' do
+ pending_plan_change = FactoryBot.create(:user_plan_change, user: user, status: 'pending', new_plan_id: plan1.id, old_plan_id: plan2.id)
+
+ second_pending_plan_change = user.user_plan_changes.new(new_plan_id: plan2, old_plan_id: plan1)
+ second_pending_plan_change.save
+
+ expect(second_pending_plan_change.errors.full_messages).to eq(['already has a pending plan change'])
+ end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 367c339e..abd860ce 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -70,4 +70,22 @@
expect(user.custom_fields).to eql(payload["custom_fields"])
end
end
+
+ describe ".current_streak" do
+ it "returns nil if user doesn't have an active subscription" do
+ expect(user.current_streak).to be_nil
+ end
+
+ it "returns 1 if user has less than one month with an active subscription" do
+ FactoryBot.create(:subscription, active: true, user: user)
+
+ expect(user.current_streak).to eq(1)
+ end
+
+ it "returns the number of months the user has with an active subscription" do
+ FactoryBot.create(:subscription, active: true, user: user, start_date: Date.today.months_ago(3))
+
+ expect(user.current_streak).to eq(3)
+ end
+ end
end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index af51dced..d6095e82 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -69,6 +69,10 @@
DatabaseCleaner.strategy = :transaction
end
+ config.before(:each, type: :job) do
+ DatabaseCleaner.strategy = :truncation
+ end
+
config.before(:each, type: :feature) do
# :rack_test driver's Rack app under test shares database connection
# with the specs, so continue to use transaction strategy for speed.