Remove payment/subscription system and card/storage limits

Fizzy is now free. Remove the entire Stripe billing system,
subscription management, and card/storage limit enforcement.

Removes from saas/: Plan model, Account::Billing, Account::Subscription,
Account::Limited, Account::OverriddenLimits, Account::BillingWaiver,
all subscription/billing controllers and views, Stripe webhook handler,
card creation/publishing limit enforcement, admin account override UI,
usage report rake task, and all related tests.

Removes from main app: Fizzy.saas? guards for subscription panel,
SaaS card footer override, near-limit notices, and saas.css stylesheet.

Adds migration to drop billing tables from the SaaS database.

Non-billing SaaS features (push notifications, signup, authorization,
telemetry, console1984/audits1984) are preserved.
This commit is contained in:
Jorge Manrubia
2026-03-17 09:22:04 +01:00
parent bead275d27
commit 964915bc66
74 changed files with 9 additions and 2221 deletions
@@ -1,29 +0,0 @@
require "test_helper"
require "ostruct"
class Account::BillingPortalsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "redirects to stripe billing portal" do
Current.account.subscription.update!(stripe_customer_id: "cus_test123")
session = OpenStruct.new(url: "https://billing.stripe.com/session123")
Stripe::BillingPortal::Session.expects(:create)
.with(customer: "cus_test123", return_url: account_settings_url)
.returns(session)
get account_billing_portal_path
assert_redirected_to "https://billing.stripe.com/session123"
end
test "requires admin" do
logout_and_sign_in_as :david
get account_billing_portal_path
assert_response :forbidden
end
end
@@ -1,73 +0,0 @@
require "test_helper"
class Account::Subscriptions::CardCreationTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :mike
end
# Nearing limits - shown in card creation footer
test "admin sees nearing card limit notice" do
accounts(:initech).update_column(:cards_count, 950)
get card_draft_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :success
assert_match /upgrade to unlimited/i, response.body
end
test "admin sees nearing storage limit notice" do
Account.any_instance.stubs(:bytes_used).returns(600.megabytes)
get card_draft_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :success
assert_match /upgrade to get more/i, response.body
end
# Exceeding limits - shown instead of create buttons
test "admin sees exceeding card limit notice" do
accounts(:initech).update_column(:cards_count, 1001)
get card_draft_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :success
assert_match /youve used your.*free cards/i, response.body
end
test "admin sees exceeding storage limit notice" do
Account.any_instance.stubs(:bytes_used).returns(1.1.gigabytes)
get card_draft_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :success
assert_match /youve run out of.*free storage/i, response.body
end
# Paid accounts under limits - no notices
test "paid account under limits sees no notices" do
logout_and_sign_in_as :kevin
accounts(:"37s").subscription.update!(plan: Plan.paid, status: :active)
get card_path(cards(:layout), script_name: accounts(:"37s").slug)
assert_response :success
assert_no_match /upgrade/i, response.body
assert_no_match /youve used your/i, response.body
end
# Comped accounts under limits - no notices
test "comped account under limits sees no notices" do
accounts(:initech).comp
get card_draft_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :success
assert_no_match /upgrade/i, response.body
assert_no_match /youve used your/i, response.body
end
end
@@ -1,35 +0,0 @@
require "test_helper"
require "ostruct"
class Account::Subscriptions::DowngradesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
accounts(:"37s").subscription.update!(stripe_subscription_id: "sub_123", plan: Plan.paid_with_extra_storage)
end
test "downgrade redirects to stripe billing portal" do
stripe_subscription = OpenStruct.new(items: OpenStruct.new(data: [ OpenStruct.new(id: "si_123") ]))
portal_session = OpenStruct.new(url: "https://billing.stripe.com/session/abc123")
Stripe::Subscription.stubs(:retrieve).with("sub_123").returns(stripe_subscription)
Stripe::BillingPortal::Session.stubs(:create).returns(portal_session)
post account_subscription_downgrade_path
assert_redirected_to "https://billing.stripe.com/session/abc123"
end
test "downgrade requires admin" do
logout_and_sign_in_as :david
post account_subscription_downgrade_path
assert_response :forbidden
end
test "downgrade requires downgradeable plan" do
accounts(:"37s").subscription.update!(plan: Plan.paid)
post account_subscription_downgrade_path
assert_response :bad_request
end
end
@@ -1,62 +0,0 @@
require "test_helper"
class Account::Subscriptions::SettingsTest < ActionDispatch::IntegrationTest
test "free users see current usage" do
sign_in_as :mike
accounts(:initech).update_column(:cards_count, 3)
get account_settings_path(script_name: accounts(:initech).slug)
assert_response :success
assert_match /Youve used.*3.*free cards out of 1,000/i, response.body
end
test "paid users see thank you message" do
sign_in_as :kevin
accounts(:"37s").subscription.update!(plan: Plan.paid, status: :active)
get account_settings_path(script_name: accounts(:"37s").slug)
assert_response :success
assert_select "h3", text: "Thank you for buying Fizzy"
end
test "regular plan users see upgrade option" do
sign_in_as :kevin
accounts(:"37s").subscription.update!(plan: Plan.paid, status: :active)
get account_settings_path(script_name: accounts(:"37s").slug)
assert_response :success
assert_select "button", text: /upgrade/i
assert_select "button", text: /downgrade/i, count: 0
end
test "extra storage plan users see downgrade option" do
sign_in_as :kevin
accounts(:"37s").subscription.update!(plan: Plan.paid_with_extra_storage, status: :active)
get account_settings_path(script_name: accounts(:"37s").slug)
assert_response :success
assert_select "button", text: /downgrade/i
end
test "comped accounts see no subscription panel" do
sign_in_as :mike
accounts(:initech).comp
get account_settings_path(script_name: accounts(:initech).slug)
assert_response :success
assert_no_match /thank you for buying/i, response.body
assert_no_match /free cards out of/i, response.body
assert_no_match /upgrade/i, response.body
assert_no_match /downgrade/i, response.body
end
end
@@ -1,35 +0,0 @@
require "test_helper"
require "ostruct"
class Account::Subscriptions::UpgradesControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
accounts(:"37s").subscription.update!(stripe_subscription_id: "sub_123", plan: Plan.paid)
end
test "upgrade redirects to stripe billing portal" do
stripe_subscription = OpenStruct.new(items: OpenStruct.new(data: [ OpenStruct.new(id: "si_123") ]))
portal_session = OpenStruct.new(url: "https://billing.stripe.com/session/abc123")
Stripe::Subscription.stubs(:retrieve).with("sub_123").returns(stripe_subscription)
Stripe::BillingPortal::Session.stubs(:create).returns(portal_session)
post account_subscription_upgrade_path
assert_redirected_to "https://billing.stripe.com/session/abc123"
end
test "upgrade requires admin" do
logout_and_sign_in_as :david
post account_subscription_upgrade_path
assert_response :forbidden
end
test "upgrade requires upgradeable plan" do
accounts(:"37s").subscription.update!(plan: Plan.paid_with_extra_storage)
post account_subscription_upgrade_path
assert_response :bad_request
end
end
@@ -1,60 +0,0 @@
require "test_helper"
require "ostruct"
class Account::SubscriptionsControllerTest < ActionDispatch::IntegrationTest
setup do
sign_in_as :kevin
end
test "show" do
get account_subscription_path
assert_response :success
end
test "show with session_id retrieves stripe session" do
Stripe::Checkout::Session.stubs(:retrieve).with("sess_123").returns(OpenStruct.new(id: "sess_123"))
get account_subscription_path(session_id: "sess_123")
assert_response :success
end
test "show requires admin" do
logout_and_sign_in_as :david
get account_subscription_path
assert_response :forbidden
end
test "create redirects to stripe checkout" do
customer = OpenStruct.new(id: "cus_test_37signals")
session = OpenStruct.new(url: "https://checkout.stripe.com/session123")
Stripe::Customer.stubs(:retrieve).returns(customer)
Stripe::Checkout::Session.stubs(:create).returns(session)
post account_subscription_path
assert_redirected_to "https://checkout.stripe.com/session123"
end
test "create requires admin" do
logout_and_sign_in_as :david
post account_subscription_path
assert_response :forbidden
end
test "create with custom plan_key redirects to stripe checkout" do
customer = OpenStruct.new(id: "cus_test_37signals")
session = OpenStruct.new(url: "https://checkout.stripe.com/session123")
Stripe::Customer.stubs(:retrieve).returns(customer)
Stripe::Checkout::Session.stubs(:create).with do |params|
params[:metadata][:plan_key] == :monthly_extra_storage_v1
end.returns(session)
post account_subscription_path(plan_key: :monthly_extra_storage_v1)
assert_redirected_to "https://checkout.stripe.com/session123"
end
end
@@ -1,54 +0,0 @@
require "test_helper"
class Admin::AccountsControllerTest < ActionDispatch::IntegrationTest
test "staff can access index" do
sign_in_as :david
untenanted do
get saas.admin_accounts_path
end
assert_response :success
end
test "search account" do
sign_in_as :david
untenanted do
post saas.admin_account_search_path, params: { q: accounts(:"37s").external_account_id }
assert_redirected_to saas.edit_admin_account_path(accounts(:"37s").external_account_id)
end
end
test "staff can edit account" do
sign_in_as :david
untenanted do
get saas.edit_admin_account_path(accounts(:"37s").external_account_id)
end
assert_response :success
end
test "staff can override card count" do
sign_in_as :david
untenanted do
patch saas.admin_account_path(accounts(:"37s").external_account_id), params: { account: { card_count: 500 } }
assert_redirected_to saas.edit_admin_account_path(accounts(:"37s").external_account_id)
end
assert_equal 500, accounts(:"37s").reload.billed_cards_count
end
test "non-staff cannot access accounts" do
sign_in_as :jz
untenanted do
patch saas.admin_account_path(accounts(:"37s").external_account_id), params: { account: { cards_count: 9999 } }
end
assert_response :forbidden
assert_not_equal 9999, accounts(:"37s").reload.cards_count
end
end
@@ -1,32 +0,0 @@
require "test_helper"
class Admin::BillingWaiversControllerTest < ActionDispatch::IntegrationTest
test "staff can comp an account" do
sign_in_as :david
account = accounts(:"37s")
assert_not account.comped?
untenanted do
post saas.admin_account_billing_waiver_path(account.external_account_id)
assert_redirected_to saas.edit_admin_account_path(account.external_account_id)
end
assert account.reload.comped?
end
test "staff can uncomp an account" do
sign_in_as :david
account = accounts(:"37s")
account.comp
assert account.comped?
untenanted do
delete saas.admin_account_billing_waiver_path(account.external_account_id)
assert_redirected_to saas.edit_admin_account_path(account.external_account_id)
end
assert_not account.reload.comped?
end
end
@@ -1,22 +0,0 @@
require "test_helper"
class Admin::OverriddenLimitsControllerTest < ActionDispatch::IntegrationTest
test "staff can reset overridden limits" do
sign_in_as :david
account = accounts(:"37s")
# First set an override
account.override_limits(card_count: 500)
assert_equal 500, account.reload.billed_cards_count
# Then reset it
untenanted do
delete saas.admin_account_overridden_limits_path(account.external_account_id)
assert_redirected_to saas.edit_admin_account_path(account.external_account_id)
end
# Verify override was removed
assert_nil account.reload.overridden_limits
assert_equal account.cards_count, account.billed_cards_count
end
end
@@ -1,55 +0,0 @@
require "test_helper"
class Card::LimitedCreationTest < ActionDispatch::IntegrationTest
test "cannot create cards via JSON when card limit exceeded" do
sign_in_as :mike
accounts(:initech).update_column(:cards_count, 1001)
assert_no_difference -> { Card.count } do
post board_cards_path(boards(:miltons_wish_list), script_name: accounts(:initech).slug, format: :json)
end
assert_response :forbidden
end
test "can create cards via HTML when card limit exceeded but they are drafts" do
sign_in_as :mike
accounts(:initech).update_column(:cards_count, 1001)
boards(:miltons_wish_list).cards.drafted.where(creator: users(:mike)).destroy_all
assert_difference -> { Card.count } do
post board_cards_path(boards(:miltons_wish_list), script_name: accounts(:initech).slug)
end
assert_response :redirect
assert Card.last.drafted?
end
test "cannot force published status via HTML when card limit exceeded" do
sign_in_as :mike
accounts(:initech).update_column(:cards_count, 1001)
boards(:miltons_wish_list).cards.drafted.where(creator: users(:mike)).destroy_all
assert_difference -> { Card.count } do
post board_cards_path(boards(:miltons_wish_list), script_name: accounts(:initech).slug), params: { card: { status: "published" } }
end
assert_response :redirect
assert Card.last.drafted?
end
test "cannot create cards via JSON when storage limit exceeded" do
sign_in_as :mike
Account.any_instance.stubs(:bytes_used).returns(1.1.gigabytes)
assert_no_difference -> { Card.count } do
post board_cards_path(boards(:miltons_wish_list), script_name: accounts(:initech).slug, format: :json)
end
assert_response :forbidden
end
end
@@ -1,25 +0,0 @@
require "test_helper"
class Card::LimitedPublishingTest < ActionDispatch::IntegrationTest
test "cannot publish cards when card limit exceeded" do
sign_in_as :mike
accounts(:initech).update_column(:cards_count, 1001)
post card_publish_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :forbidden
assert cards(:unfinished_thoughts).reload.drafted?
end
test "cannot publish cards when storage limit exceeded" do
sign_in_as :mike
Account.any_instance.stubs(:bytes_used).returns(1.1.gigabytes)
post card_publish_path(cards(:unfinished_thoughts), script_name: accounts(:initech).slug)
assert_response :forbidden
assert cards(:unfinished_thoughts).reload.drafted?
end
end
@@ -1,100 +0,0 @@
require "test_helper"
require "ostruct"
class Stripe::WebhooksControllerTest < ActionDispatch::IntegrationTest
setup do
@account = Account.create!(name: "Test")
@subscription = @account.create_subscription! \
plan_key: "monthly_v1",
status: "incomplete",
stripe_customer_id: "cus_test123"
end
test "invalid signature returns bad request" do
Stripe::Webhook.stubs(:construct_event).raises(Stripe::SignatureVerificationError.new("invalid", "sig"))
post stripe_webhooks_path
assert_response :bad_request
end
test "checkout session completed activates subscription" do
stripe_sub = OpenStruct.new(id: "sub_123", customer: "cus_test123", status: "active", cancel_at: nil, items: stub_items(1.month.from_now.to_i))
event = stripe_event("checkout.session.completed",
mode: "subscription",
customer: "cus_test123",
subscription: "sub_123",
metadata: { "plan_key" => "monthly_v1" }
)
Stripe::Webhook.stubs(:construct_event).returns(event)
Stripe::Subscription.stubs(:retrieve).returns(stripe_sub)
Stripe::Invoice.stubs(:create_preview).returns(OpenStruct.new(amount_due: 1999))
post stripe_webhooks_path
assert_response :ok
@subscription.reload
assert_equal "sub_123", @subscription.stripe_subscription_id
assert_equal "active", @subscription.status
end
test "subscription updated changes status and syncs next amount due" do
@subscription.update!(stripe_subscription_id: "sub_123", status: "active")
stripe_sub = OpenStruct.new(
id: "sub_123",
customer: "cus_test123",
status: "past_due",
cancel_at: nil,
items: stub_items(1.month.from_now.to_i)
)
event = stripe_event("customer.subscription.updated", id: "sub_123")
Stripe::Webhook.stubs(:construct_event).returns(event)
Stripe::Subscription.stubs(:retrieve).returns(stripe_sub)
Stripe::Invoice.stubs(:create_preview).returns(OpenStruct.new(amount_due: 1999))
post stripe_webhooks_path
assert_response :ok
@subscription.reload
assert_equal "past_due", @subscription.status
assert_equal 1999, @subscription.next_amount_due_in_cents
end
test "subscription deleted cancels subscription" do
@subscription.update!(stripe_subscription_id: "sub_123", status: "active")
stripe_sub = OpenStruct.new(
id: "sub_123",
customer: "cus_test123",
status: "canceled",
cancel_at: nil,
items: stub_items(1.month.from_now.to_i)
)
event = stripe_event("customer.subscription.deleted", id: "sub_123")
Stripe::Webhook.stubs(:construct_event).returns(event)
Stripe::Subscription.stubs(:retrieve).returns(stripe_sub)
post stripe_webhooks_path
assert_response :ok
@subscription.reload
assert_equal "canceled", @subscription.status
assert_nil @subscription.stripe_subscription_id
assert_nil @subscription.next_amount_due_in_cents
end
private
def stripe_event(type, **attributes)
OpenStruct.new(type: type, data: OpenStruct.new(object: OpenStruct.new(attributes)))
end
def stub_items(current_period_end)
OpenStruct.new(data: [ OpenStruct.new(current_period_end: current_period_end) ])
end
end
-11
View File
@@ -1,11 +0,0 @@
_fixture:
model_class: Account::Subscription
signals_monthly:
id: <%= ActiveRecord::FixtureSet.identify("signals_monthly", :uuid) %>
account_id: <%= ActiveRecord::FixtureSet.identify("37s", :uuid) %>
plan_key: monthly_v1
status: active
stripe_customer_id: cus_test_37signals
created_at: <%= Time.current %>
updated_at: <%= Time.current %>
-93
View File
@@ -1,93 +0,0 @@
require "test_helper"
class Account::BillingTest < ActiveSupport::TestCase
test "plan reflects active subscription" do
account = accounts(:initech)
# No subscription
assert_equal Plan.free, account.plan
# Subscription but it is not active
account.create_subscription!(plan_key: "monthly_v1", status: "canceled", stripe_customer_id: "cus_test")
assert_equal Plan.free, account.plan
# Active subscription exists
account.subscription.update!(status: "active")
assert_equal Plan.paid, account.plan
end
test "comped account" do
account = accounts(:"37s")
assert_not account.comped?
account.comp
assert account.comped?
# Calling comp again does not create duplicate
account.comp
assert_equal 1, Account::BillingWaiver.where(account: account).count
end
test "cancel callback pauses subscription" do
account = accounts(:"37s")
user = users(:david)
subscription = mock("subscription")
subscription.expects(:pause).once
account.stubs(:subscription).returns(subscription)
account.cancel(initiated_by: user)
end
test "reactivate callback resumes subscription" do
account = accounts(:"37s")
user = users(:david)
# First cancel with a subscription mock
subscription_for_cancel = mock("subscription")
subscription_for_cancel.expects(:pause).once
account.stubs(:subscription).returns(subscription_for_cancel)
account.cancel(initiated_by: user)
# Now stub for reactivation
subscription_for_reactivate = mock("subscription")
subscription_for_reactivate.expects(:resume).once
account.stubs(:subscription).returns(subscription_for_reactivate)
account.reactivate
end
test "incinerate callback cancels subscription before destroying account" do
account = accounts(:"37s")
subscription = mock("subscription")
subscription.expects(:cancel).once
account.stubs(:subscription).returns(subscription)
account.incinerate
end
test "owner_email_changed enqueues sync job when subscription exists" do
account = accounts(:"37s")
account.create_subscription!(
stripe_customer_id: "cus_test",
plan_key: "monthly_v1",
status: "active"
)
assert_enqueued_with(job: Account::SyncStripeCustomerEmailJob, args: [ account.subscription ]) do
account.owner_email_changed
end
end
test "owner_email_changed does nothing without subscription" do
account = accounts(:initech)
account.subscription&.destroy
assert_no_enqueued_jobs do
account.owner_email_changed
end
end
end
-122
View File
@@ -1,122 +0,0 @@
require "test_helper"
class Account::LimitedTest < ActiveSupport::TestCase
test "detect nearing card limit" do
# Paid plans are never limited
accounts(:"37s").update_column(:cards_count, 1_000_000)
assert_not accounts(:"37s").nearing_plan_cards_limit?
# Free plan not near limit
accounts(:initech).update_column(:cards_count, 899)
assert_not accounts(:initech).nearing_plan_cards_limit?
# Free plan at exactly the threshold (remaining = 100)
accounts(:initech).update_column(:cards_count, 900)
assert accounts(:initech).nearing_plan_cards_limit?
# Free plan over the threshold
accounts(:initech).update_column(:cards_count, 901)
assert accounts(:initech).nearing_plan_cards_limit?
end
test "detect exceeding card limit" do
# Paid plans are never limited
accounts(:"37s").update_column(:cards_count, 1_000_000)
assert_not accounts(:"37s").exceeding_card_limit?
# Free plan under limit
accounts(:initech).update_column(:cards_count, 999)
assert_not accounts(:initech).exceeding_card_limit?
# Free plan at exactly the limit
accounts(:initech).update_column(:cards_count, 1000)
assert accounts(:initech).exceeding_card_limit?
# Free plan over limit
accounts(:initech).update_column(:cards_count, 1001)
assert accounts(:initech).exceeding_card_limit?
end
test "override limits" do
account = accounts(:initech)
account.update_column(:cards_count, 1001)
assert account.exceeding_card_limit?
assert_equal 1001, account.billed_cards_count
account.override_limits card_count: 500
assert_not account.exceeding_card_limit?
assert_equal 500, account.billed_cards_count
assert_equal 1001, account.cards_count # original unchanged
account.reset_overridden_limits
assert account.exceeding_card_limit?
assert_equal 1001, account.billed_cards_count
end
test "comped accounts are never limited" do
account = accounts(:initech)
account.update_column(:cards_count, 1_000_000)
assert account.exceeding_card_limit?
assert account.nearing_plan_cards_limit?
account.comp
assert_not account.exceeding_card_limit?
assert_not account.nearing_plan_cards_limit?
end
test "uncomping an account restores limits" do
account = accounts(:initech)
account.update_column(:cards_count, 1_000_000)
account.comp
assert_not account.exceeding_card_limit?
account.uncomp
assert account.exceeding_card_limit?
end
test "detect nearing storage limit" do
# Paid plans have large storage limits
accounts(:"37s").stubs(:bytes_used).returns(4.gigabytes)
assert_not accounts(:"37s").nearing_plan_storage_limit?
# Free plan not near limit
accounts(:initech).stubs(:bytes_used).returns(400.megabytes)
assert_not accounts(:initech).nearing_plan_storage_limit?
# Free plan near limit
accounts(:initech).stubs(:bytes_used).returns(600.megabytes)
assert accounts(:initech).nearing_plan_storage_limit?
end
test "detect exceeding storage limit" do
# Free plan under limit
accounts(:initech).stubs(:bytes_used).returns(900.megabytes)
assert_not accounts(:initech).exceeding_storage_limit?
# Free plan over limit
accounts(:initech).stubs(:bytes_used).returns(1.1.gigabytes)
assert accounts(:initech).exceeding_storage_limit?
end
test "override bytes_used limits" do
account = accounts(:initech)
account.stubs(:bytes_used).returns(1.1.gigabytes)
assert account.exceeding_storage_limit?
assert_equal 1.1.gigabytes, account.billed_bytes_used
account.override_limits bytes_used: 500.megabytes
assert_not account.exceeding_storage_limit?
assert_equal 500.megabytes, account.billed_bytes_used
assert_equal 1.1.gigabytes, account.bytes_used # original unchanged
account.reset_overridden_limits
assert account.exceeding_storage_limit?
assert_equal 1.1.gigabytes, account.billed_bytes_used
end
end
@@ -1,245 +0,0 @@
require "test_helper"
class Account::SubscriptionTest < ActiveSupport::TestCase
test "get the account plan" do
subscription = Account::Subscription.new(plan_key: "free_v1")
assert_equal Plan[:free_v1], subscription.plan
end
test "check if account is active" do
subscription = Account::Subscription.new(status: "active")
assert subscription.active?
end
test "check if account is paid" do
assert Account::Subscription.new(plan_key: "monthly_v1", status: "active").paid?
assert_not Account::Subscription.new(plan_key: "free_v1", status: "active").paid?
end
test "paid scope includes active paid subscriptions" do
subscription = Account::Subscription.create!(
account: accounts(:initech), plan_key: "monthly_v1", status: "active",
stripe_customer_id: "cus_paid"
)
assert_includes Account::Subscription.paid, subscription
end
test "paid scope includes trialing and past_due subscriptions" do
trialing = Account::Subscription.create!(
account: accounts(:initech), plan_key: "monthly_v1", status: "trialing",
stripe_customer_id: "cus_trialing"
)
past_due = Account::Subscription.create!(
account: accounts(:acme), plan_key: "monthly_extra_storage_v1", status: "past_due",
stripe_customer_id: "cus_past_due"
)
assert_includes Account::Subscription.paid, trialing
assert_includes Account::Subscription.paid, past_due
end
test "paid scope excludes free plan subscriptions" do
subscription = Account::Subscription.create!(
account: accounts(:initech), plan_key: "free_v1", status: "active",
stripe_customer_id: "cus_free"
)
assert_not_includes Account::Subscription.paid, subscription
end
test "paid scope excludes canceled subscriptions" do
subscription = Account::Subscription.create!(
account: accounts(:initech), plan_key: "monthly_v1", status: "canceled",
stripe_customer_id: "cus_canceled"
)
assert_not_includes Account::Subscription.paid, subscription
end
test "paid scope excludes comped accounts" do
subscription = Account::Subscription.create!(
account: accounts(:initech), plan_key: "monthly_v1", status: "active",
stripe_customer_id: "cus_comped"
)
Account::BillingWaiver.create!(account: accounts(:initech))
assert_not_includes Account::Subscription.paid, subscription
end
test "pause pauses Stripe subscription with void behavior" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.expects(:update).with(
"sub_123",
pause_collection: { behavior: "void" }
).returns(true)
subscription.pause
end
test "pause does nothing when no stripe_subscription_id" do
subscription = Account::Subscription.new(stripe_subscription_id: nil)
Stripe::Subscription.expects(:update).never
subscription.pause
end
test "pause raises on Stripe errors" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.stubs(:update).raises(
Stripe::APIConnectionError.new("Network error")
)
assert_raises Stripe::APIConnectionError do
subscription.pause
end
end
test "resume resumes Stripe subscription" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.expects(:update).with(
"sub_123",
pause_collection: ""
).returns(true)
subscription.resume
end
test "resume does nothing when no stripe_subscription_id" do
subscription = Account::Subscription.new(stripe_subscription_id: nil)
Stripe::Subscription.expects(:update).never
subscription.resume
end
test "resume raises on Stripe errors" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.stubs(:update).raises(
Stripe::AuthenticationError.new("Invalid API key")
)
assert_raises Stripe::AuthenticationError do
subscription.resume
end
end
test "cancel cancels Stripe subscription" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.expects(:cancel).with("sub_123").returns(true)
subscription.cancel
end
test "cancel does nothing when no stripe_subscription_id" do
subscription = Account::Subscription.new(stripe_subscription_id: nil)
Stripe::Subscription.expects(:cancel).never
subscription.cancel
end
test "cancel treats 404 as success" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_deleted")
Stripe::Subscription.stubs(:cancel).raises(
Stripe::InvalidRequestError.new("No such subscription", {})
)
assert_nothing_raised do
subscription.cancel
end
end
test "cancel raises on other Stripe errors" do
subscription = Account::Subscription.new(stripe_subscription_id: "sub_123")
Stripe::Subscription.stubs(:cancel).raises(
Stripe::RateLimitError.new("Rate limit exceeded")
)
assert_raises Stripe::RateLimitError do
subscription.cancel
end
end
test "sync_customer_email_to_stripe updates Stripe customer with owner email" do
account = accounts(:"37s")
owner = account.users.find_by(role: :owner) || account.users.first.tap { |u| u.update!(role: :owner) }
subscription = account.create_subscription!(
stripe_customer_id: "cus_test",
plan_key: "monthly_v1",
status: "active"
)
Stripe::Customer.expects(:update).with("cus_test", email: owner.identity.email_address).once
subscription.sync_customer_email_to_stripe
end
test "sync_customer_email_to_stripe does nothing without stripe_customer_id" do
account = accounts(:"37s")
subscription = account.build_subscription(
stripe_customer_id: nil,
plan_key: "free_v1",
status: "active"
)
Stripe::Customer.expects(:update).never
subscription.sync_customer_email_to_stripe
end
test "sync_customer_email_to_stripe does nothing without owner" do
account = accounts(:"37s")
account.users.update_all(role: :member)
subscription = account.create_subscription!(
stripe_customer_id: "cus_test",
plan_key: "monthly_v1",
status: "active"
)
Stripe::Customer.expects(:update).never
subscription.sync_customer_email_to_stripe
end
test "sync_customer_email_to_stripe does nothing when owner has no identity" do
account = accounts(:"37s")
owner = account.users.find_by(role: :owner) || account.users.first.tap { |u| u.update!(role: :owner) }
owner.update_column(:identity_id, nil)
subscription = account.create_subscription!(
stripe_customer_id: "cus_test",
plan_key: "monthly_v1",
status: "active"
)
Stripe::Customer.expects(:update).never
subscription.sync_customer_email_to_stripe
end
test "sync_customer_email_to_stripe treats deleted customer as success" do
account = accounts(:"37s")
account.users.find_by(role: :owner) || account.users.first.tap { |u| u.update!(role: :owner) }
subscription = account.create_subscription!(
stripe_customer_id: "cus_deleted",
plan_key: "monthly_v1",
status: "active"
)
Stripe::Customer.stubs(:update).raises(
Stripe::InvalidRequestError.new("No such customer", {})
)
assert_nothing_raised do
subscription.sync_customer_email_to_stripe
end
end
end
-18
View File
@@ -1,18 +0,0 @@
require "test_helper"
class PlanTest < ActiveSupport::TestCase
test "free plan is free" do
assert Plan[:free_v1].free?
end
test "monthly plan is not free" do
assert_not Plan[:monthly_v1].free?
end
test "find plan by its price id" do
Plan.paid.stubs(:stripe_price_id).returns("price_monthly_v1")
assert_equal Plan.paid, Plan.find_by_price_id("price_monthly_v1")
assert_nil Plan.find_by_price_id("unknown_price_id")
end
end
@@ -1,51 +0,0 @@
require "test_helper"
class User::NotifiesAccountOfEmailChangeTest < ActiveSupport::TestCase
setup do
@account = accounts(:"37s")
@owner = @account.users.find_by(role: :owner) || @account.users.first.tap { |u| u.update!(role: :owner) }
@member = @account.users.where.not(id: @owner.id).first || @account.users.create!(
name: "Member",
identity: Identity.create!(email_address: "member@example.com"),
role: :member
)
end
test "notifies account when owner changes email" do
@account.expects(:owner_email_changed).once
new_identity = Identity.create!(email_address: "new-owner@example.com")
@owner.update!(identity: new_identity)
end
test "does not notify account when non-owner changes email" do
@account.expects(:owner_email_changed).never
new_identity = Identity.create!(email_address: "new-member@example.com")
@member.update!(identity: new_identity)
end
test "does not notify account when owner is deactivated" do
@account.expects(:owner_email_changed).never
@owner.update!(identity: nil)
end
test "does not notify account when identity unchanged" do
@account.expects(:owner_email_changed).never
@owner.update!(name: "New Name")
end
test "notifies account when user becomes owner" do
@account.expects(:owner_email_changed).once
@member.update!(role: :owner)
end
test "does not notify account when owner becomes member" do
@account.expects(:owner_email_changed).never
@owner.update!(role: :member)
end
end