Files
fizzy/saas/test/models/account/subscription_test.rb
T
Mike Dalessio ab5283441d SaaS usage reporting (#2690)
* Add saas:usage_report rake task and extract Subscription.paid scope

Add a rake task to generate a CSV usage report with per-account data:
Queenbee ID, sign up date, paid date, card count, storage used, and
last active date.

Extract the paid subscriptions query from Admin::StatsController into
an Account::Subscription.paid scope so both the controller and the
new rake task can share it.

* Add comped and account name columns to usage report

* Update saas/lib/tasks/fizzy/usage_report.rake

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Preload storage_total to avoid N+1 in usage report

* Batch last_active_at queries to avoid per-account aggregates

* Add tests for Account::Subscription.paid scope

* Update saas/app/models/account/subscription.rb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Aggregate paid dates in SQL instead of loading all subscriptions

* Fix paid scope to derive plan keys from Plan.all instead of PLANS hash

* Move paid dates and comped lookups into per-batch queries

* Materialize batch IDs to avoid cross-database subquery

SaasRecord models live on a separate database (fizzy_saas) that doesn't
have the accounts table. Using batch.select(:id) generated a subquery
that ran on the saas database, causing a table-not-found error. Using
pluck(:id) materializes the IDs into an array instead.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-03-11 10:11:13 -04:00

246 lines
7.3 KiB
Ruby

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