ab5283441d
* 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>
246 lines
7.3 KiB
Ruby
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
|