1011 lines
35 KiB
Elixir
1011 lines
35 KiB
Elixir
defmodule Plausible.Billing.QuotaTest do
|
|
alias Plausible.Billing.EnterprisePlan
|
|
use Plausible.DataCase, async: true
|
|
use Plausible
|
|
alias Plausible.Billing.{Quota, Plans}
|
|
alias Plausible.Billing.Feature.{Goals, Props, SitesAPI, StatsAPI, Teams, SharedLinks}
|
|
|
|
use Plausible.Teams.Test
|
|
|
|
on_ee do
|
|
alias Plausible.Billing.Feature.Funnels
|
|
alias Plausible.Billing.Feature.RevenueGoals
|
|
end
|
|
|
|
@legacy_plan_id "558746"
|
|
@v1_plan_id "558018"
|
|
@v2_plan_id "654177"
|
|
@v3_plan_id "749342"
|
|
@v4_1m_plan_id "857101"
|
|
@v4_10m_growth_plan_id "857104"
|
|
@v4_10m_business_plan_id "857112"
|
|
|
|
@highest_growth_plan Plausible.Billing.Plans.find(@v4_10m_growth_plan_id)
|
|
@highest_business_plan Plausible.Billing.Plans.find(@v4_10m_business_plan_id)
|
|
|
|
on_ee do
|
|
@v3_business_plan_id "857481"
|
|
|
|
describe "site_limit/1" do
|
|
test "returns 50 when user is on an old plan" do
|
|
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
|
|
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of()
|
|
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
|
|
|
|
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v1)
|
|
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v2)
|
|
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v3)
|
|
end
|
|
|
|
test "returns 50 when user is on free_10k plan" do
|
|
team = new_user() |> subscribe_to_plan("free_10k") |> team_of()
|
|
assert 50 == Plausible.Teams.Billing.site_limit(team)
|
|
end
|
|
|
|
test "returns the configured site limit for enterprise plan" do
|
|
team = new_user() |> subscribe_to_enterprise_plan(site_limit: 500) |> team_of()
|
|
assert Plausible.Teams.Billing.site_limit(team) == 500
|
|
end
|
|
|
|
test "returns 10 when user in on trial" do
|
|
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) |> team_of()
|
|
assert Plausible.Teams.Billing.site_limit(team) == 10
|
|
end
|
|
|
|
test "returns the subscription limit for enterprise users who have not paid yet" do
|
|
team =
|
|
new_user()
|
|
|> subscribe_to_plan(@v1_plan_id)
|
|
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|
|
|> team_of()
|
|
|
|
assert Plausible.Teams.Billing.site_limit(team) == 50
|
|
end
|
|
|
|
test "returns 10 for enterprise users who have not upgraded yet and are on trial" do
|
|
team =
|
|
new_user()
|
|
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|
|
|> team_of()
|
|
|
|
assert Plausible.Teams.Billing.site_limit(team) == 10
|
|
end
|
|
|
|
test "grandfathered site limit should be unlimited when accepting transfer invitations" do
|
|
# must be before ~D[2021-05-05]
|
|
owner = new_user(team: [inserted_at: ~N[2021-01-01 00:00:00]])
|
|
# plan with site_limit: 10
|
|
subscribe_to_plan(owner, "857097")
|
|
_site = for _ <- 1..10, do: new_site(owner: owner)
|
|
|
|
other_owner = new_user()
|
|
other_site = new_site(owner: other_owner)
|
|
invite_transfer(other_site, owner, inviter: other_owner)
|
|
|
|
team = owner |> team_of()
|
|
|
|
assert Plausible.Teams.Billing.site_limit(team) == :unlimited
|
|
assert Plausible.Teams.Invitations.ensure_can_take_ownership(other_site, team) == :ok
|
|
end
|
|
end
|
|
end
|
|
|
|
test "site_usage/1 returns the amount of sites the user owns" do
|
|
user = new_user()
|
|
for _ <- 1..3, do: new_site(owner: user)
|
|
add_guest(new_site(), user: user, role: :editor)
|
|
add_guest(new_site(), user: user, role: :viewer)
|
|
team = team_of(user)
|
|
|
|
assert Plausible.Teams.Billing.site_usage(team) == 3
|
|
end
|
|
|
|
describe "below_limit?/2" do
|
|
test "returns true when quota is not exceeded" do
|
|
assert Quota.below_limit?(3, 5)
|
|
end
|
|
|
|
test "returns true when limit is :unlimited" do
|
|
assert Quota.below_limit?(10_000, :unlimited)
|
|
end
|
|
|
|
test "returns false when usage is at limit" do
|
|
refute Quota.below_limit?(3, 3)
|
|
end
|
|
|
|
test "returns false when usage exceeds the limit" do
|
|
refute Quota.below_limit?(10, 3)
|
|
end
|
|
end
|
|
|
|
describe "ensure_within_plan_limits/2" do
|
|
test "returns :ok when site and team member limits are reached but not exceeded" do
|
|
usage = %{
|
|
monthly_pageviews: %{last_30_days: %{total: 1}},
|
|
team_members: 3,
|
|
sites: 10
|
|
}
|
|
|
|
plan = Plans.find(@v4_1m_plan_id)
|
|
|
|
assert Quota.ensure_within_plan_limits(usage, plan) == :ok
|
|
end
|
|
|
|
test "returns all exceeded limits" do
|
|
usage = %{
|
|
monthly_pageviews: %{last_30_days: %{total: 1_150_001}},
|
|
team_members: 4,
|
|
sites: 11
|
|
}
|
|
|
|
plan = Plans.find(@v4_1m_plan_id)
|
|
|
|
{:error, {:over_plan_limits, exceeded_limits}} =
|
|
Quota.ensure_within_plan_limits(usage, plan)
|
|
|
|
assert :monthly_pageview_limit in exceeded_limits
|
|
assert :team_member_limit in exceeded_limits
|
|
assert :site_limit in exceeded_limits
|
|
end
|
|
|
|
test "can skip checking the pageview limit" do
|
|
usage = %{
|
|
monthly_pageviews: %{last_30_days: %{total: 1_150_001}},
|
|
team_members: 2,
|
|
sites: 8
|
|
}
|
|
|
|
plan = Plans.find(@v4_1m_plan_id)
|
|
|
|
assert :ok = Quota.ensure_within_plan_limits(usage, plan, ignore_pageview_limit: true)
|
|
end
|
|
|
|
test "by the last 30 days usage, pageview limit is exceeded when more than 10% over the limit" do
|
|
usage_within_pageview_limit = %{
|
|
monthly_pageviews: %{last_30_days: %{total: 1_100_000}},
|
|
team_members: 1,
|
|
sites: 1
|
|
}
|
|
|
|
usage_over_pageview_limit = %{
|
|
monthly_pageviews: %{last_30_days: %{total: 1_100_001}},
|
|
team_members: 1,
|
|
sites: 1
|
|
}
|
|
|
|
plan = Plans.find(@v4_1m_plan_id)
|
|
|
|
assert Quota.ensure_within_plan_limits(usage_within_pageview_limit, plan) == :ok
|
|
|
|
assert Quota.ensure_within_plan_limits(usage_over_pageview_limit, plan) ==
|
|
{:error, {:over_plan_limits, [:monthly_pageview_limit]}}
|
|
end
|
|
|
|
test "by billing cycles usage, pageview limit is exceeded when last two billing cycles exceed by 10%" do
|
|
usage_within_pageview_limit = %{
|
|
monthly_pageviews: %{penultimate_cycle: %{total: 11_000}, last_cycle: %{total: 10_999}},
|
|
team_members: 1,
|
|
sites: 1
|
|
}
|
|
|
|
usage_over_pageview_limit = %{
|
|
monthly_pageviews: %{penultimate_cycle: %{total: 11_000}, last_cycle: %{total: 11_000}},
|
|
team_members: 1,
|
|
sites: 1
|
|
}
|
|
|
|
plan = Plans.find(@v3_plan_id)
|
|
|
|
assert Quota.ensure_within_plan_limits(usage_within_pageview_limit, plan) == :ok
|
|
|
|
assert Quota.ensure_within_plan_limits(usage_over_pageview_limit, plan) ==
|
|
{:error, {:over_plan_limits, [:monthly_pageview_limit]}}
|
|
end
|
|
|
|
test "returns error with exceeded limits for enterprise plans" do
|
|
user = new_user()
|
|
|
|
usage = %{
|
|
monthly_pageviews: %{penultimate_cycle: %{total: 1}, last_cycle: %{total: 1}},
|
|
team_members: 1,
|
|
sites: 2
|
|
}
|
|
|
|
subscribe_to_enterprise_plan(user,
|
|
subscription?: false,
|
|
paddle_plan_id: "whatever",
|
|
site_limit: 1
|
|
)
|
|
|
|
enterprise_plan = Repo.get_by!(EnterprisePlan, paddle_plan_id: "whatever")
|
|
|
|
assert Quota.ensure_within_plan_limits(usage, enterprise_plan) ==
|
|
{:error, {:over_plan_limits, [:site_limit]}}
|
|
end
|
|
end
|
|
|
|
describe "monthly_pageview_limit/1" do
|
|
test "is based on the plan if user is on a legacy plan" do
|
|
team =
|
|
new_user()
|
|
|> subscribe_to_plan(@legacy_plan_id)
|
|
|> team_of()
|
|
|> Plausible.Teams.with_subscription()
|
|
|
|
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 1_000_000
|
|
end
|
|
|
|
test "is based on the plan if user is on a standard plan" do
|
|
team =
|
|
new_user()
|
|
|> subscribe_to_plan(@v1_plan_id)
|
|
|> team_of()
|
|
|> Plausible.Teams.with_subscription()
|
|
|
|
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 10_000
|
|
end
|
|
|
|
test "free_10k has 10k monthly_pageview_limit" do
|
|
team =
|
|
new_user()
|
|
|> subscribe_to_plan("free_10k")
|
|
|> team_of()
|
|
|> Plausible.Teams.with_subscription()
|
|
|
|
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 10_000
|
|
end
|
|
|
|
test "is based on the enterprise plan if user is on an enterprise plan" do
|
|
user = new_user()
|
|
|
|
subscription =
|
|
user
|
|
|> subscribe_to_enterprise_plan(monthly_pageview_limit: 100_000)
|
|
|> team_of()
|
|
|> Repo.preload(:subscription)
|
|
|> Map.fetch!(:subscription)
|
|
|
|
assert Plausible.Teams.Billing.monthly_pageview_limit(subscription) == 100_000
|
|
end
|
|
|
|
test "does not limit pageviews when user has a pending enterprise plan" do
|
|
user = new_user()
|
|
|
|
subscription =
|
|
user
|
|
|> subscribe_to_plan("pending-enterprise")
|
|
|> team_of()
|
|
|> Repo.preload(:subscription)
|
|
|> Map.fetch!(:subscription)
|
|
|
|
assert Plausible.Teams.Billing.monthly_pageview_limit(subscription) == :unlimited
|
|
end
|
|
end
|
|
|
|
describe "team_member_usage/2" do
|
|
test "returns the number of members in all of the sites the user owns" do
|
|
me = new_user()
|
|
site_i_own_1 = new_site(owner: me)
|
|
add_guest(site_i_own_1, role: :viewer)
|
|
site_i_own_2 = new_site(owner: me)
|
|
add_guest(site_i_own_2, role: :editor)
|
|
add_guest(site_i_own_2, role: :viewer)
|
|
_site_i_own_3 = new_site(owner: me)
|
|
site_i_have_access = new_site()
|
|
add_guest(site_i_have_access, user: me, role: :viewer)
|
|
add_guest(site_i_have_access, role: :viewer)
|
|
add_guest(site_i_have_access, role: :viewer)
|
|
add_guest(site_i_have_access, role: :viewer)
|
|
team = team_of(me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 3
|
|
end
|
|
|
|
test "counts the same email address as one team member" do
|
|
me = new_user()
|
|
joe = new_user(email: "joe@plausible.test")
|
|
site_i_own_1 = new_site(owner: me)
|
|
add_guest(site_i_own_1, user: joe, role: :viewer)
|
|
site_i_own_2 = new_site(owner: me)
|
|
add_guest(site_i_own_2, user: new_user(), role: :editor)
|
|
add_guest(site_i_own_2, user: joe, role: :viewer)
|
|
site_i_own_3 = new_site(owner: me)
|
|
invite_guest(site_i_own_3, joe, role: :viewer, inviter: me)
|
|
team = team_of(me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 2
|
|
end
|
|
|
|
test "counts pending invitations as team members" do
|
|
me = new_user()
|
|
member = new_user()
|
|
site_i_own = new_site(owner: me)
|
|
add_guest(site_i_own, user: member, role: :editor)
|
|
site_i_have_access = new_site()
|
|
add_guest(site_i_have_access, user: me, role: :editor)
|
|
team = team_of(me)
|
|
|
|
invite_guest(site_i_own, new_user(), role: :viewer, inviter: me)
|
|
invite_guest(site_i_own, new_user(), role: :viewer, inviter: member)
|
|
invite_guest(site_i_have_access, new_user(), role: :viewer, inviter: me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 3
|
|
end
|
|
|
|
test "does not count ownership transfer as a team member by default" do
|
|
me = new_user()
|
|
site_i_own = new_site(owner: me)
|
|
invite_transfer(site_i_own, new_user(), inviter: me)
|
|
team = team_of(me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 0
|
|
end
|
|
|
|
test "counts team members from pending ownerships when specified" do
|
|
me = new_user(trial_expiry_date: Date.utc_today())
|
|
my_team = team_of(me)
|
|
|
|
user_1 = new_user()
|
|
user_2 = new_user()
|
|
|
|
pending_ownership_site = new_site(owner: user_1)
|
|
add_guest(pending_ownership_site, user: user_2, role: :editor)
|
|
|
|
invite_transfer(pending_ownership_site, me, inviter: user_1)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(my_team,
|
|
pending_ownership_site_ids: [pending_ownership_site.id]
|
|
) == 2
|
|
end
|
|
|
|
test "counts invitations towards team members from pending ownership sites" do
|
|
me = new_user(trial_expiry_date: Date.utc_today())
|
|
user_1 = new_user()
|
|
user_2 = new_user()
|
|
pending_ownership_site = new_site(owner: user_1)
|
|
invite_transfer(pending_ownership_site, me, inviter: user_1)
|
|
invite_guest(pending_ownership_site, user_2, role: :editor, inviter: user_1)
|
|
team = team_of(me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team,
|
|
pending_ownership_site_ids: [pending_ownership_site.id]
|
|
) == 2
|
|
end
|
|
|
|
test "returns zero when user does not have any site" do
|
|
team = new_user() |> team_of()
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 0
|
|
end
|
|
|
|
test "does not count email report recipients as team members" do
|
|
me = new_user()
|
|
site = new_site(owner: me)
|
|
team = team_of(me)
|
|
|
|
insert(:weekly_report,
|
|
site: site,
|
|
recipients: ["adam@plausible.test", "vini@plausible.test"]
|
|
)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 0
|
|
end
|
|
|
|
test "excludes specific emails from limit calculation" do
|
|
me = new_user()
|
|
member = new_user()
|
|
site_i_own = new_site(owner: me)
|
|
add_guest(site_i_own, user: member, role: :editor)
|
|
team = team_of(me)
|
|
|
|
invite_guest(site_i_own, new_user(), role: :viewer, inviter: me)
|
|
invite_guest(site_i_own, new_user(), role: :viewer, inviter: member)
|
|
invitation = invite_guest(site_i_own, "foo@example.com", role: :viewer, inviter: me)
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team) == 4
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team,
|
|
exclude_emails: ["arbitrary@example.com"]
|
|
) == 4
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team, exclude_emails: [member.email]) == 3
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team,
|
|
exclude_emails: [invitation.team_invitation.email]
|
|
) ==
|
|
3
|
|
|
|
assert Plausible.Teams.Billing.team_member_usage(team,
|
|
exclude_emails: [member.email, invitation.team_invitation.email]
|
|
) == 2
|
|
end
|
|
end
|
|
|
|
on_ee do
|
|
describe "team_member_limit/1" do
|
|
test "returns unlimited when user is on an old plan" do
|
|
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
|
|
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of()
|
|
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
|
|
|
|
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v1)
|
|
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v2)
|
|
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v3)
|
|
end
|
|
|
|
test "returns unlimited when user is on free_10k plan" do
|
|
user = new_user()
|
|
subscribe_to_plan(user, "free_10k")
|
|
team = team_of(user)
|
|
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team)
|
|
end
|
|
|
|
test "returns 5 when user in on trial" do
|
|
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) |> team_of()
|
|
|
|
assert 3 == Plausible.Teams.Billing.team_member_limit(team)
|
|
end
|
|
|
|
test "returns the enterprise plan limit" do
|
|
user = new_user()
|
|
subscribe_to_enterprise_plan(user, team_member_limit: 27)
|
|
team = team_of(user)
|
|
|
|
assert 27 == Plausible.Teams.Billing.team_member_limit(team)
|
|
end
|
|
|
|
test "reads from json file when the user is on a v4 plan" do
|
|
team_on_growth = new_user() |> subscribe_to_growth_plan() |> team_of()
|
|
team_on_business = new_user() |> subscribe_to_business_plan() |> team_of()
|
|
|
|
assert 3 == Plausible.Teams.Billing.team_member_limit(team_on_growth)
|
|
assert 10 == Plausible.Teams.Billing.team_member_limit(team_on_business)
|
|
end
|
|
|
|
test "returns unlimited when user is on a v3 business plan" do
|
|
team = new_user() |> subscribe_to_plan(@v3_business_plan_id) |> team_of()
|
|
|
|
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team)
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "features_usage/2" do
|
|
test "returns an empty list for a user/site who does not use any feature" do
|
|
assert [] == Plausible.Teams.Billing.features_usage(team_of(new_user()))
|
|
assert [] == Plausible.Teams.Billing.features_usage(nil, [new_site().id])
|
|
end
|
|
|
|
test "returns [Props] when user/site uses custom props" do
|
|
user = new_user()
|
|
site = new_site(owner: user, allowed_event_props: ["dummy"])
|
|
team = team_of(user)
|
|
|
|
assert [Props] == Plausible.Teams.Billing.features_usage(nil, [site.id])
|
|
assert [Props] == Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
|
|
on_ee do
|
|
test "returns [Funnels] when user/site uses funnels" do
|
|
user = new_user()
|
|
site = new_site(owner: user)
|
|
team = team_of(user)
|
|
|
|
goals = insert_list(3, :goal, site: site, event_name: fn -> Ecto.UUID.generate() end)
|
|
steps = Enum.map(goals, &%{"goal_id" => &1.id})
|
|
Plausible.Funnels.create(site, "dummy", steps)
|
|
|
|
assert [Funnels] == Plausible.Teams.Billing.features_usage(nil, [site.id])
|
|
assert [Funnels] == Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
|
|
test "returns [RevenueGoals] when user/site uses revenue goals" do
|
|
user = new_user(trial_expiry_date: Date.utc_today())
|
|
team = team_of(user)
|
|
site = new_site(owner: user)
|
|
insert(:goal, currency: :USD, site: site, event_name: "Purchase")
|
|
|
|
assert [RevenueGoals] == Plausible.Teams.Billing.features_usage(nil, [site.id])
|
|
assert [RevenueGoals] == Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
end
|
|
|
|
test "returns [StatsAPI] when user has a stats api key" do
|
|
user = new_user(trial_expiry_date: Date.utc_today())
|
|
team = team_of(user)
|
|
insert(:api_key, user: user)
|
|
|
|
assert [StatsAPI] == Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
|
|
test "returns [SitesAPI] when user has a Sites API enabled api key" do
|
|
user =
|
|
new_user()
|
|
|> subscribe_to_enterprise_plan(features: [StatsAPI, SitesAPI])
|
|
|
|
team = team_of(user)
|
|
|
|
insert(:api_key, user: user, scopes: ["sites:provision:*"])
|
|
|
|
assert [StatsAPI, SitesAPI] == Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
|
|
test "returns feature usage based on a user and a custom list of site_ids" do
|
|
user = new_user(trial_expiry_date: Date.utc_today())
|
|
team = team_of(user)
|
|
insert(:api_key, user: user)
|
|
site_using_props = new_site(allowed_event_props: ["dummy"])
|
|
|
|
site_ids = [site_using_props.id]
|
|
assert [Props, StatsAPI] == Plausible.Teams.Billing.features_usage(team, site_ids)
|
|
end
|
|
|
|
on_ee do
|
|
test "returns multiple features used by the user" do
|
|
user = new_user()
|
|
insert(:api_key, user: user)
|
|
|
|
site =
|
|
new_site(
|
|
allowed_event_props: ["dummy"],
|
|
owner: user
|
|
)
|
|
|
|
team = team_of(user)
|
|
|
|
insert(:goal, currency: :USD, site: site, event_name: "Purchase")
|
|
|
|
goals = insert_list(3, :goal, site: site, event_name: fn -> Ecto.UUID.generate() end)
|
|
steps = Enum.map(goals, &%{"goal_id" => &1.id})
|
|
Plausible.Funnels.create(site, "dummy", steps)
|
|
|
|
assert [Props, Funnels, RevenueGoals, StatsAPI] ==
|
|
Plausible.Teams.Billing.features_usage(team)
|
|
end
|
|
end
|
|
|
|
test "accounts only for sites the user owns" do
|
|
assert [] == Plausible.Teams.Billing.features_usage(nil)
|
|
end
|
|
end
|
|
|
|
describe "allowed_features_for/1" do
|
|
on_ee do
|
|
test "users with expired trials have no access to subscription features" do
|
|
team = new_user(trial_expiry_date: ~D[2023-01-01]) |> team_of()
|
|
assert [Goals] == Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
end
|
|
|
|
test "returns all grandfathered features when user is on an old plan" do
|
|
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
|
|
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of()
|
|
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
|
|
|
|
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team_on_v1)
|
|
|
|
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team_on_v2)
|
|
|
|
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team_on_v3)
|
|
end
|
|
|
|
test "returns [Goals, Props, StatsAPI] when user is on free_10k plan" do
|
|
user = new_user()
|
|
subscribe_to_plan(user, "free_10k")
|
|
team = team_of(user)
|
|
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
|
|
on_ee do
|
|
test "returns the enterprise plan features" do
|
|
user = new_user()
|
|
|
|
subscribe_to_enterprise_plan(user,
|
|
monthly_pageview_limit: 100_000,
|
|
site_limit: 500,
|
|
features: [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels]
|
|
)
|
|
|
|
team = team_of(user)
|
|
|
|
assert [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
end
|
|
|
|
test "returns all features when user in on trial" do
|
|
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) |> team_of()
|
|
|
|
assert Plausible.Billing.Feature.list() -- [Plausible.Billing.Feature.SitesAPI] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
|
|
test "returns previous plan limits for enterprise users who have not paid yet" do
|
|
user =
|
|
new_user()
|
|
|> subscribe_to_plan(@v1_plan_id)
|
|
|> subscribe_to_enterprise_plan(subscription?: false)
|
|
|
|
team = team_of(user)
|
|
|
|
assert [Goals, Props, StatsAPI, Teams, SharedLinks] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
|
|
test "returns all features for enterprise users who have not upgraded yet and are on trial" do
|
|
team = new_user() |> subscribe_to_enterprise_plan(subscription?: false) |> team_of()
|
|
|
|
assert Plausible.Billing.Feature.list() -- [Plausible.Billing.Feature.SitesAPI] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
|
|
test "returns old plan features for enterprise customers who are due to change a plan" do
|
|
user = new_user()
|
|
|
|
subscribe_to_enterprise_plan(user,
|
|
paddle_plan_id: "old-paddle-plan-id",
|
|
features: [Plausible.Billing.Feature.StatsAPI]
|
|
)
|
|
|
|
subscribe_to_enterprise_plan(user,
|
|
paddle_plan_id: "new-paddle-plan-id",
|
|
subscription?: false
|
|
)
|
|
|
|
team = team_of(user)
|
|
|
|
assert [Plausible.Billing.Feature.StatsAPI] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
|
|
test "returns SitesAPI feature for enterprise customers with appropriate plan" do
|
|
user = new_user()
|
|
|
|
subscribe_to_enterprise_plan(user,
|
|
features: [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.SitesAPI]
|
|
)
|
|
|
|
team = team_of(user)
|
|
|
|
assert [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.SitesAPI] ==
|
|
Plausible.Teams.Billing.allowed_features_for(team)
|
|
end
|
|
end
|
|
|
|
describe "monthly_pageview_usage/2" do
|
|
test "returns empty usage for user without subscription and without any sites" do
|
|
team = new_user() |> team_of()
|
|
|
|
assert %{
|
|
last_30_days: %{
|
|
total: 0,
|
|
custom_events: 0,
|
|
pageviews: 0,
|
|
date_range: date_range
|
|
}
|
|
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
|
|
|
|
assert date_range.last == Date.utc_today()
|
|
assert Date.compare(date_range.first, date_range.last) == :lt
|
|
end
|
|
|
|
test "returns usage for user without subscription with a site" do
|
|
user = new_user()
|
|
site = new_site(owner: user)
|
|
team = team_of(user)
|
|
|
|
now = NaiveDateTime.utc_now()
|
|
|
|
populate_stats(site, [
|
|
build(:event, timestamp: Timex.shift(now, days: -40), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -10), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -9), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -8), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -7), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -6), name: "custom")
|
|
])
|
|
|
|
assert %{
|
|
last_30_days: %{
|
|
total: 5,
|
|
custom_events: 2,
|
|
pageviews: 3,
|
|
date_range: %{}
|
|
}
|
|
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
|
|
end
|
|
|
|
test "engagement events are not counted towards monthly pageview usage" do
|
|
user = new_user()
|
|
site = new_site(owner: user)
|
|
team = team_of(user)
|
|
now = NaiveDateTime.utc_now()
|
|
|
|
populate_stats(site, [
|
|
build(:event, timestamp: Timex.shift(now, days: -8), name: "custom"),
|
|
build(:pageview, user_id: 199, timestamp: Timex.shift(now, days: -5, minutes: -2)),
|
|
build(:engagement, user_id: 199, timestamp: Timex.shift(now, days: -5))
|
|
])
|
|
|
|
assert %{
|
|
last_30_days: %{
|
|
total: 2,
|
|
custom_events: 1,
|
|
pageviews: 1,
|
|
date_range: %{}
|
|
}
|
|
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
|
|
end
|
|
|
|
test "returns usage for user with subscription and a site" do
|
|
today = Date.utc_today()
|
|
user = new_user()
|
|
subscribe_to_growth_plan(user, last_bill_date: Date.shift(today, day: -8))
|
|
team = team_of(user)
|
|
|
|
site = new_site(owner: user)
|
|
|
|
now = NaiveDateTime.utc_now()
|
|
|
|
populate_stats(site, [
|
|
build(:event, timestamp: Timex.shift(now, days: -40), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -10), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -9), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -8), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -7), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -6), name: "custom")
|
|
])
|
|
|
|
assert %{
|
|
current_cycle: %{
|
|
total: 3,
|
|
custom_events: 1,
|
|
pageviews: 2,
|
|
date_range: %{}
|
|
},
|
|
last_cycle: %{
|
|
total: 2,
|
|
custom_events: 1,
|
|
pageviews: 1,
|
|
date_range: %{}
|
|
},
|
|
penultimate_cycle: %{
|
|
total: 1,
|
|
custom_events: 1,
|
|
pageviews: 0,
|
|
date_range: %{}
|
|
}
|
|
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
|
|
end
|
|
|
|
test "returns usage for only a subset of site IDs" do
|
|
today = Date.utc_today()
|
|
|
|
user = new_user()
|
|
subscribe_to_growth_plan(user, last_bill_date: Date.shift(today, day: -8))
|
|
team = team_of(user)
|
|
|
|
site1 = new_site(owner: user)
|
|
site2 = new_site(owner: user)
|
|
site3 = new_site(owner: user)
|
|
|
|
now = NaiveDateTime.utc_now()
|
|
|
|
for site <- [site1, site2, site3] do
|
|
populate_stats(site, [
|
|
build(:event, timestamp: Timex.shift(now, days: -40), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -10), name: "custom"),
|
|
build(:event, timestamp: Timex.shift(now, days: -9), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -8), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -7), name: "pageview"),
|
|
build(:event, timestamp: Timex.shift(now, days: -6), name: "custom")
|
|
])
|
|
end
|
|
|
|
assert %{
|
|
current_cycle: %{
|
|
total: 6,
|
|
custom_events: 2,
|
|
pageviews: 4,
|
|
date_range: %{}
|
|
},
|
|
last_cycle: %{
|
|
total: 4,
|
|
custom_events: 2,
|
|
pageviews: 2,
|
|
date_range: %{}
|
|
},
|
|
penultimate_cycle: %{
|
|
total: 2,
|
|
custom_events: 2,
|
|
pageviews: 0,
|
|
date_range: %{}
|
|
}
|
|
} = Plausible.Teams.Billing.monthly_pageview_usage(team, [site1.id, site3.id])
|
|
end
|
|
end
|
|
|
|
describe "usage_cycle/1" do
|
|
setup do
|
|
user = new_user()
|
|
site = new_site(owner: user)
|
|
team = team_of(user)
|
|
|
|
populate_stats(site, [
|
|
build(:event, timestamp: ~N[2023-04-01 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-04-02 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-04-03 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-04-04 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-04-05 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-05-01 00:00:00], name: "pageview"),
|
|
build(:event, timestamp: ~N[2023-05-02 00:00:00], name: "pageview"),
|
|
build(:event, timestamp: ~N[2023-05-03 00:00:00], name: "pageview"),
|
|
build(:event, timestamp: ~N[2023-05-04 00:00:00], name: "pageview"),
|
|
build(:event, timestamp: ~N[2023-05-05 00:00:00], name: "pageview"),
|
|
build(:event, timestamp: ~N[2023-06-01 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-06-02 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-06-03 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-06-04 00:00:00], name: "custom"),
|
|
build(:event, timestamp: ~N[2023-06-05 00:00:00], name: "custom")
|
|
])
|
|
|
|
{:ok, %{user: user, team: team}}
|
|
end
|
|
|
|
test "returns usage and date_range for the given billing month", %{user: user, team: team} do
|
|
last_bill_date = ~D[2023-06-03]
|
|
today = ~D[2023-06-05]
|
|
|
|
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
|
|
|
|
assert %{date_range: penultimate_cycle, pageviews: 2, custom_events: 3, total: 5} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
|
|
|
|
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
|
|
|
|
assert %{date_range: current_cycle, pageviews: 0, custom_events: 3, total: 3} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
|
|
|
|
assert penultimate_cycle == Date.range(~D[2023-04-03], ~D[2023-05-02])
|
|
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
|
|
assert current_cycle == Date.range(~D[2023-06-03], ~D[2023-07-02])
|
|
end
|
|
|
|
test "returns usage and date_range for the last 30 days", %{team: team} do
|
|
today = ~D[2023-06-01]
|
|
|
|
assert %{date_range: last_30_days, pageviews: 4, custom_events: 1, total: 5} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
|
|
|
|
assert last_30_days == Date.range(~D[2023-05-02], ~D[2023-06-01])
|
|
end
|
|
|
|
test "only considers sites that the user owns", %{user: user, team: team} do
|
|
different_site = new_site()
|
|
add_guest(different_site, user: user, role: :editor)
|
|
|
|
populate_stats(different_site, [
|
|
build(:event, timestamp: ~N[2023-05-05 00:00:00], name: "custom")
|
|
])
|
|
|
|
last_bill_date = ~D[2023-06-03]
|
|
today = ~D[2023-06-05]
|
|
|
|
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
|
|
|
|
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
|
|
|
|
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
|
|
end
|
|
|
|
test "in case of yearly billing, cycles are normalized as if they were paying monthly" do
|
|
last_bill_date = ~D[2020-09-01]
|
|
today = ~D[2021-02-02]
|
|
|
|
user = new_user()
|
|
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
|
|
team = team_of(user)
|
|
|
|
assert %{date_range: penultimate_cycle} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
|
|
|
|
assert %{date_range: last_cycle} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
|
|
|
|
assert %{date_range: current_cycle} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
|
|
|
|
assert penultimate_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])
|
|
assert last_cycle == Date.range(~D[2021-01-01], ~D[2021-01-31])
|
|
assert current_cycle == Date.range(~D[2021-02-01], ~D[2021-02-28])
|
|
end
|
|
|
|
test "returns correct billing months when last_bill_date is the first day of the year" do
|
|
last_bill_date = ~D[2021-01-01]
|
|
today = ~D[2021-01-02]
|
|
|
|
user = new_user()
|
|
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
|
|
team = team_of(user)
|
|
|
|
assert %{date_range: penultimate_cycle, total: 0} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
|
|
|
|
assert %{date_range: last_cycle, total: 0} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
|
|
|
|
assert %{date_range: current_cycle, total: 0} =
|
|
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
|
|
|
|
assert penultimate_cycle == Date.range(~D[2020-11-01], ~D[2020-11-30])
|
|
assert last_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])
|
|
assert current_cycle == Date.range(~D[2021-01-01], ~D[2021-01-31])
|
|
end
|
|
end
|
|
|
|
describe "suggest_tier/2" do
|
|
setup do
|
|
user = new_user()
|
|
team = user |> team_of() |> Plausible.Teams.with_subscription()
|
|
%{user: user, team: team}
|
|
end
|
|
|
|
test "returns nil if usage doesn't have any sites", %{team: team} do
|
|
suggested_tier =
|
|
team
|
|
|> Plausible.Teams.Billing.quota_usage(with_features: true)
|
|
|> Quota.suggest_tier(@highest_growth_plan, @highest_business_plan, nil)
|
|
|
|
assert suggested_tier == nil
|
|
end
|
|
|
|
test "returns :custom if the monthly pageview limit exceeds regular plans",
|
|
%{team: team} do
|
|
suggested_tier =
|
|
team
|
|
|> Plausible.Teams.Billing.quota_usage(with_features: true)
|
|
|> Map.merge(%{monthly_pageviews: %{last_30_days: %{total: 12_000_000}}, sites: 1})
|
|
|> Quota.suggest_tier(@highest_growth_plan, @highest_business_plan, nil)
|
|
|
|
assert suggested_tier == :custom
|
|
end
|
|
|
|
test "returns :growth if usage within growth limits",
|
|
%{team: team} do
|
|
suggested_tier =
|
|
team
|
|
|> Plausible.Teams.Billing.quota_usage(with_features: true)
|
|
|> Map.put(:sites, 1)
|
|
|> Quota.suggest_tier(@highest_growth_plan, @highest_business_plan, nil)
|
|
|
|
assert suggested_tier == :growth
|
|
end
|
|
|
|
test "returns :business if usage within growth limits but already on a business plan",
|
|
%{team: team} do
|
|
suggested_tier =
|
|
team
|
|
|> Plausible.Teams.Billing.quota_usage(with_features: true)
|
|
|> Map.put(:sites, 1)
|
|
|> Quota.suggest_tier(@highest_growth_plan, @highest_business_plan, :business)
|
|
|
|
assert suggested_tier == :business
|
|
end
|
|
|
|
test "returns :business if business features used",
|
|
%{team: team} do
|
|
suggested_tier =
|
|
team
|
|
|> Plausible.Teams.Billing.quota_usage(with_features: true)
|
|
|> Map.merge(%{sites: 1, features: [Plausible.Billing.Feature.Funnels]})
|
|
|> Quota.suggest_tier(@highest_growth_plan, @highest_business_plan, nil)
|
|
|
|
assert suggested_tier == :business
|
|
end
|
|
end
|
|
end
|