Skip to content

Commit 2047bc7

Browse files
cursoragentjaracursorsh
andcommitted
Add edge cases tests for Profitable metrics and calculations
Co-authored-by: jaracursorsh <jaracursorsh@mitomail.com>
1 parent 995a702 commit 2047bc7

File tree

1 file changed

+146
-0
lines changed

1 file changed

+146
-0
lines changed

test/profitable/edge_cases_test.rb

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# frozen_string_literal: true
2+
require "test_helper"
3+
4+
class EdgeCasesTest < Minitest::Test
5+
def setup
6+
super
7+
@customer = Pay::Customer.create!(processor: "stripe")
8+
end
9+
10+
def create_subscription(attrs = {})
11+
defaults = {
12+
customer: @customer,
13+
processor: "stripe",
14+
status: "active",
15+
quantity: 1,
16+
current_period_start: Time.now - 15.days,
17+
current_period_end: Time.now + 15.days,
18+
data: { "subscription_items" => [{ "price" => { "unit_amount" => 1000, "recurring" => { "interval" => "month", "interval_count" => 1 } } }] }
19+
}
20+
Pay::Subscription.create!(defaults.merge(attrs))
21+
end
22+
23+
def test_estimated_valuation_clamps_multiplier_bounds
24+
create_subscription # mrr 1000, arr 12000 cents
25+
26+
# Below lower bound -> clamp to 0.1x
27+
v = Profitable.estimated_valuation(at: "0x")
28+
assert_equal (12_000 * 0.1).round, v
29+
30+
# Above upper bound -> clamp to 100x
31+
v = Profitable.estimated_valuation(at: "200x")
32+
assert_equal (12_000 * 100).round, v
33+
34+
# Invalid type -> default 3x
35+
v = Profitable.estimated_valuation(at: :wtf)
36+
assert_equal (12_000 * 3).round, v
37+
end
38+
39+
def test_time_to_next_mrr_milestone_no_more_milestones
40+
# Set current mrr to highest milestone or beyond
41+
Profitable.stub(:mrr, Profitable::NumericResult.new(100_000_000 * 100)) do
42+
msg = Profitable.time_to_next_mrr_milestone
43+
assert_match /Congratulations!/, msg
44+
end
45+
end
46+
47+
def test_time_to_next_mrr_milestone_unable_without_positive_growth
48+
Profitable.stub(:mrr, Profitable::NumericResult.new(1_000 * 100)) do
49+
Profitable.stub(:calculate_mrr_growth_rate, 0) do
50+
msg = Profitable.time_to_next_mrr_milestone
51+
assert_match /Unable to calculate/, msg
52+
end
53+
Profitable.stub(:calculate_mrr_growth_rate, -10) do
54+
msg = Profitable.time_to_next_mrr_milestone
55+
assert_match /Unable to calculate/, msg
56+
end
57+
end
58+
end
59+
60+
def test_recurring_revenue_percentage_zero_when_no_total_revenue
61+
assert_equal 0, Profitable.recurring_revenue_percentage(in_the_last: 30.days)
62+
end
63+
64+
def test_churn_is_zero_when_no_subscribers_at_start
65+
assert_equal 0, Profitable.churn(in_the_last: 30.days)
66+
end
67+
68+
def test_new_mrr_counts_subscriptions_created_at_period_start
69+
start = 10.days
70+
t = Time.now
71+
Timecop.freeze(t - start) do
72+
# created exactly at start boundary (inclusive)
73+
create_subscription(created_at: t - start)
74+
end
75+
# for newly created at start, prorated days should be full period
76+
val = Profitable.new_mrr(in_the_last: start)
77+
assert_operator val, :>=, 0
78+
end
79+
80+
def test_mrr_at_includes_subscriptions_created_at_date
81+
t = Time.now
82+
Timecop.freeze(t - 1.day) do
83+
create_subscription(created_at: t - 1.day)
84+
end
85+
# created_at == date should be included
86+
val = Profitable.send(:calculate_mrr_at, t - 1.day)
87+
assert_operator val, :>, 0
88+
end
89+
90+
def test_churned_mrr_ignores_subscriptions_ending_in_future
91+
Timecop.freeze(Time.now - 10.days) do
92+
create_subscription
93+
end
94+
sub = Pay::Subscription.last
95+
sub.update!(status: "canceled", ends_at: Time.now + 10.days)
96+
assert_equal 0, Profitable.churned_mrr(in_the_last: 30.days)
97+
end
98+
99+
def test_average_revenue_per_customer_zero_when_no_paying_customers
100+
assert_equal 0, Profitable.average_revenue_per_customer
101+
end
102+
103+
def test_lifetime_value_zero_when_no_customers_or_zero_churn
104+
# no customers
105+
assert_equal 0, Profitable.lifetime_value
106+
107+
# add customers but zero churn
108+
Timecop.freeze(Time.now - 35.days) do
109+
create_subscription
110+
end
111+
# churn rate computed as zero
112+
Profitable.stub(:churn, Profitable::NumericResult.new(0, :percentage)) do
113+
assert_equal 0, Profitable.lifetime_value
114+
end
115+
end
116+
117+
def test_normalize_to_monthly_handles_nil_and_case_insensitive_intervals
118+
base = Profitable::Processors::Base.new(nil)
119+
assert_equal 0, base.send(:normalize_to_monthly, nil, "month", 1)
120+
assert_equal 0, base.send(:normalize_to_monthly, 1000, nil, 1)
121+
assert_equal 0, base.send(:normalize_to_monthly, 1000, "month", nil)
122+
123+
assert_in_delta 1000.0, base.send(:normalize_to_monthly, 1000, "Month", 1), 0.001
124+
assert_in_delta 41.6667, base.send(:normalize_to_monthly, 1000, "year", 2), 0.001
125+
assert_in_delta 30_000.0, base.send(:normalize_to_monthly, 1000, "day", 1), 0.001
126+
assert_in_delta 4_000.0, base.send(:normalize_to_monthly, 1000, "week", 1), 0.001
127+
end
128+
129+
def test_negative_amounts_are_clamped_to_zero_in_mrr
130+
data = {
131+
"subscription_items" => [
132+
{ "price" => { "unit_amount" => -500, "recurring" => { "interval" => "month", "interval_count" => 1 } } }
133+
]
134+
}
135+
sub = Pay::Subscription.create!(
136+
customer: @customer,
137+
processor: "stripe",
138+
status: "active",
139+
quantity: 1,
140+
current_period_start: Time.now - 15.days,
141+
current_period_end: Time.now + 15.days,
142+
data: data
143+
)
144+
assert_equal 0, Profitable::MrrCalculator.process_subscription(sub)
145+
end
146+
end

0 commit comments

Comments
 (0)