505 lines
15 KiB
Elixir
505 lines
15 KiB
Elixir
defmodule Plausible.Workers.TrafficChangeNotifierTest do
|
|
use Plausible.DataCase, async: true
|
|
use Bamboo.Test
|
|
alias Plausible.Workers.TrafficChangeNotifier
|
|
|
|
@view_dashboard_text "View dashboard"
|
|
@review_installation_text "review your installation"
|
|
|
|
describe "drops" do
|
|
test "does not notify anyone if we've stopped accepting traffic for the owner" do
|
|
user = new_user(team: [accept_traffic_until: Date.utc_today()])
|
|
site = new_site(owner: user)
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: ["jerod@example.com", "uku@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
|
|
test "does notify if threshold reached and we're accepting traffic" do
|
|
user = new_user(team: [accept_traffic_until: Date.utc_today() |> Date.add(+1)])
|
|
site = new_site(owner: user)
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: ["jerod@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic drop on #{site.domain}",
|
|
to: [nil: "jerod@example.com"]
|
|
)
|
|
end
|
|
|
|
test "does not notify anyone if current visitors does not drop below notification threshold" do
|
|
site = insert(:site)
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 2,
|
|
recipients: ["jerod@example.com", "uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(2)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
|
|
test "notifies all recipients when traffic drops under configured threshold" do
|
|
site = new_site()
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: ["jerod@example.com", "uku@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic drop on #{site.domain}",
|
|
to: [nil: "jerod@example.com"]
|
|
)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic drop on #{site.domain}",
|
|
to: [nil: "uku@example.com"]
|
|
)
|
|
end
|
|
|
|
test "includes dashboard and installation links only when recipient is guest or team member" do
|
|
owner = new_user()
|
|
{:ok, team} = Plausible.Teams.get_or_create(owner)
|
|
site = new_site(team: team)
|
|
team_member = add_member(team, role: :admin)
|
|
viewer_guest = add_guest(site, role: :viewer, user: new_user())
|
|
random_email = "random@example.com"
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: [owner.email, team_member.email, viewer_guest.email, random_email]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
expected_subject = "Traffic drop on #{site.domain}"
|
|
|
|
four_emails =
|
|
for _ <- 1..4 do
|
|
assert_delivered_email_matches(%{
|
|
html_body: html_body,
|
|
subject: ^expected_subject,
|
|
to: [nil: email]
|
|
})
|
|
|
|
%{to: email, html_body: html_body}
|
|
end
|
|
|
|
{[random_recipient_email], site_member_emails} =
|
|
Enum.split_with(four_emails, &(&1.to == random_email))
|
|
|
|
refute random_recipient_email.html_body =~ @view_dashboard_text
|
|
|
|
Enum.each(site_member_emails, fn email ->
|
|
assert email.html_body =~ @view_dashboard_text
|
|
assert email.html_body =~ @review_installation_text
|
|
end)
|
|
end
|
|
|
|
on_ee do
|
|
test "does not link to site installation in a consolidated view traffic drop notification" do
|
|
%{email: user_email} = user = new_user()
|
|
{:ok, team} = Plausible.Teams.get_or_create(user)
|
|
new_site(team: team)
|
|
new_site(team: team)
|
|
|
|
consolidated_view = new_consolidated_view(team)
|
|
|
|
insert(:drop_notification,
|
|
site: consolidated_view,
|
|
threshold: 10,
|
|
recipients: [user.email]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{
|
|
html_body: html_body,
|
|
subject: "Traffic drop on your sites",
|
|
to: [nil: ^user_email]
|
|
})
|
|
|
|
assert html_body =~ "across all your sites"
|
|
assert html_body =~ @view_dashboard_text
|
|
refute html_body =~ @review_installation_text
|
|
end
|
|
|
|
test "does not notify traffic drop on consolidated view when ok_to_display? is false" do
|
|
user = new_user()
|
|
{:ok, team} = Plausible.Teams.get_or_create(user)
|
|
site = new_site(team: team)
|
|
new_site(team: team)
|
|
|
|
consolidated_view = new_consolidated_view(team)
|
|
|
|
Plausible.Repo.delete(site)
|
|
|
|
refute Plausible.ConsolidatedView.ok_to_display?(team)
|
|
|
|
insert(:drop_notification,
|
|
site: consolidated_view,
|
|
threshold: 10,
|
|
recipients: [user.email]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
end
|
|
|
|
test "does not send notifications more than once every 12 hours" do
|
|
site = new_site()
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 1,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil, ~N[2021-01-01 00:00:00])
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic drop on #{site.domain}",
|
|
to: [nil: "uku@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil, ~N[2021-01-01 11:59:59])
|
|
|
|
assert_no_emails_delivered()
|
|
|
|
TrafficChangeNotifier.perform(nil, ~N[2021-01-01 12:00:01])
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic drop on #{site.domain}",
|
|
to: [nil: "uku@example.com"]
|
|
)
|
|
end
|
|
|
|
test "adds settings link if recipient has access to the site" do
|
|
user = new_user(email: "robert@example.com")
|
|
site = new_site(domain: "example.com", owner: user)
|
|
|
|
insert(:drop_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: ["robert@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_email_delivered_with(
|
|
html_body: ~r|http://localhost:8000/example.com/installation\?flow=review|
|
|
)
|
|
end
|
|
end
|
|
|
|
describe "spikes" do
|
|
test "does not notify anyone if current visitors does not exceed notification threshold" do
|
|
site = insert(:site)
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 2,
|
|
recipients: ["jerod@example.com", "uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [build(:pageview, timestamp: minutes_ago(1))])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
|
|
test "notifies all recipients when traffic is higher than configured threshold" do
|
|
site = new_site()
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 2,
|
|
recipients: ["jerod@example.com", "uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(3)),
|
|
build(:pageview, timestamp: minutes_ago(2)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic spike on #{site.domain}",
|
|
to: [nil: "jerod@example.com"]
|
|
)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic spike on #{site.domain}",
|
|
to: [nil: "uku@example.com"]
|
|
)
|
|
end
|
|
|
|
on_ee do
|
|
test "notifies traffic spike on consolidated view" do
|
|
{:ok, team} = new_user() |> Plausible.Teams.get_or_create()
|
|
site1 = new_site(team: team)
|
|
site2 = new_site(team: team)
|
|
|
|
consolidated_view = new_consolidated_view(team)
|
|
|
|
insert(:spike_notification,
|
|
site: consolidated_view,
|
|
threshold: 2,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site1, [
|
|
build(:pageview, referrer_source: "Google", pathname: "/b", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "Google", pathname: "/a", timestamp: minutes_ago(2))
|
|
])
|
|
|
|
populate_stats(site2, [
|
|
build(:pageview, referrer_source: "Twitter", pathname: "/a", timestamp: minutes_ago(3))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{
|
|
html_body: html_body,
|
|
subject: "Traffic spike on your sites",
|
|
to: [nil: "uku@example.com"]
|
|
})
|
|
|
|
assert html_body =~ "across all your sites"
|
|
|
|
assert html_body =~ "The top sources for current visitors:"
|
|
assert html_body =~ "<b>2</b> visitors from <b>Google</b>"
|
|
assert html_body =~ "<b>1</b> visitor from <b>Twitter</b>"
|
|
|
|
assert html_body =~ "Your top pages being visited:"
|
|
assert html_body =~ "<b>2</b> visitors on <b>/a</b>"
|
|
assert html_body =~ "<b>1</b> visitor on <b>/b</b>"
|
|
end
|
|
|
|
test "does not notify traffic spike on consolidated view when ok_to_display? is false" do
|
|
user = new_user()
|
|
{:ok, team} = Plausible.Teams.get_or_create(user)
|
|
site1 = new_site(team: team)
|
|
site2 = new_site(team: team)
|
|
|
|
consolidated_view = new_consolidated_view(team)
|
|
|
|
insert(:spike_notification,
|
|
site: consolidated_view,
|
|
threshold: 2,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site1, [
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(2)),
|
|
build(:pageview, timestamp: minutes_ago(3))
|
|
])
|
|
|
|
Plausible.Repo.delete(site2)
|
|
|
|
refute Plausible.ConsolidatedView.ok_to_display?(team)
|
|
|
|
insert(:drop_notification,
|
|
site: consolidated_view,
|
|
threshold: 10,
|
|
recipients: [user.email]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
end
|
|
|
|
test "ignores 'Direct / None' source" do
|
|
site = new_site()
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 5,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, referrer_source: "A", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "A", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "A", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "B", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "B", timestamp: minutes_ago(1)),
|
|
build(:pageview, referrer_source: "C", timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{
|
|
to: [nil: "uku@example.com"],
|
|
html_body: html_body
|
|
})
|
|
|
|
assert html_body =~ "The top sources for current visitors:"
|
|
assert html_body =~ "<b>3</b> visitors from <b>A</b>"
|
|
assert html_body =~ "<b>2</b> visitors from <b>B</b>"
|
|
assert html_body =~ "<b>1</b> visitor from <b>C</b>"
|
|
assert html_body =~ "There are currently <b>8</b>"
|
|
end
|
|
|
|
test "does not list sources at all when everything is 'Direct / None'" do
|
|
site = new_site()
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 1,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{
|
|
to: [nil: "uku@example.com"],
|
|
html_body: html_body
|
|
})
|
|
|
|
refute html_body =~ "The top sources for current visitors:"
|
|
assert html_body =~ "There are currently <b>2</b>"
|
|
end
|
|
|
|
test "includes top 3 pages" do
|
|
site = new_site()
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 10,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, pathname: "/one", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/one", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/one", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/one", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/two", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/two", timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/two", timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, pathname: "/not-this-one", timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{
|
|
to: [nil: "uku@example.com"],
|
|
html_body: html_body
|
|
})
|
|
|
|
assert html_body =~ "There are currently <b>10</b>"
|
|
|
|
assert html_body =~ "Your top pages being visited:"
|
|
assert html_body =~ "<b>4</b> visitors on <b>/one</b>"
|
|
assert html_body =~ "<b>3</b> visitors on <b>/two</b>"
|
|
assert html_body =~ "<b>2</b> visitors on <b>/</b>"
|
|
|
|
refute html_body =~ "/not-this-one"
|
|
end
|
|
|
|
test "does not check site if it is locked" do
|
|
site = new_site()
|
|
site.team |> Ecto.Changeset.change(locked: true) |> Repo.update!()
|
|
|
|
insert(:spike_notification,
|
|
site: site,
|
|
threshold: 1,
|
|
recipients: ["uku@example.com"]
|
|
)
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
|
|
test "does not send notifications more than once every 12 hours" do
|
|
site = new_site()
|
|
insert(:spike_notification, site: site, threshold: 1, recipients: ["uku@example.com"])
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_email_delivered_with(
|
|
subject: "Traffic spike on #{site.domain}",
|
|
to: [nil: "uku@example.com"]
|
|
)
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_no_emails_delivered()
|
|
end
|
|
|
|
test "adds a dashboard link if recipient has access to the site" do
|
|
user = new_user(email: "robert@example.com")
|
|
site = new_site(domain: "example.com", owner: user)
|
|
insert(:spike_notification, site: site, threshold: 1, recipients: ["robert@example.com"])
|
|
|
|
populate_stats(site, [
|
|
build(:pageview, timestamp: minutes_ago(1)),
|
|
build(:pageview, timestamp: minutes_ago(1))
|
|
])
|
|
|
|
TrafficChangeNotifier.perform(nil)
|
|
|
|
assert_delivered_email_matches(%{html_body: html_body})
|
|
assert html_body =~ @view_dashboard_text
|
|
end
|
|
end
|
|
|
|
def minutes_ago(min) do
|
|
NaiveDateTime.utc_now(:second) |> NaiveDateTime.add(-min, :minute)
|
|
end
|
|
end
|