analytics/test/plausible_web/controllers/api/external_controller_test.exs
hq1 d2f2c69387
Conditionally support switching between v1 and v2 clickhouse schemas (#2780)
* Remove ClickhouseSetup module

This has been an implicit point of contact to many
tests. From now on the goal is for each test to maintain
its own, isolated setup so that no accidental clashes
and implicit assumptions are relied upon.

* Implement v2 schema check

An environment variable V2_MIGRATION_DONE acts like
a feature flag, switching plausible from using old events/sessions
schemas to v2 schemas introduced by NumericIDs migration.

* Run both test suites sequentially

While the code for v1 and v2 schemas must be kept still,
we will from now on run tests against both code paths.
Secondary test run will set V2_MIGRATION_DONE=1 variable,
thus making all `Plausible.v2?()` checks return `true'.

* Remove unused function

This is a remnant from the short period when
we would check for existing events before allowing
creating a new site.

* Update test setups/factories with v2 migration check

* Make GateKeeper return site id along with :allow

* Make Billing module check for v2 schema

* Make ingestion aware of v2 schema

* Disable site transfers for when v2 is live

In a separate changeset we will implement simplified
site transfer for when v2 migration is complete.
The new transfer will only rename the site domain in postgres
and keep track of the original site prior to the transfer
so we keep an ingestion grace period until the customers
redeploy their scripting.

* Make Stats base queries aware of v2 schema switch

* Update breakdown with v2 conditionals

* Update pageview local start with v2 check

* Update current visitoris with v2 check

* Update stats controller with v2 checks

* Update external controller with v2 checks

* Update remaining tests with proper fixtures

* Rewrite redundant assignment

* Remove unused alias

* Mute credo, this is not the right time

* Add test_helper prompt

* Fetch priv dir so it works with a release

* Fetch distinct partitions only

* Don't limit inspect output for partitions

* Ensure SQL is printed to IO

* Remove redundant domain fixture
2023-03-27 13:52:42 +02:00

1291 lines
33 KiB
Elixir

defmodule PlausibleWeb.Api.ExternalControllerTest do
use PlausibleWeb.ConnCase
use Plausible.ClickhouseRepo
@user_agent "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36"
@user_agent_mobile "Mozilla/5.0 (Linux; Android 6.0; U007 Pro Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/44.0.2403.119 Mobile Safari/537.36"
@user_agent_tablet "Mozilla/5.0 (Linux; U; Android 4.2.2; it-it; Surfing TAB B 9.7 3G Build/JDQ39) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30"
describe "POST /api/event" do
setup do
site = insert(:site)
{:ok, site: site}
end
test "records the event", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "http://gigride.live/",
referrer: "http://m.facebook.com/"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.hostname == "gigride.live"
if Plausible.v2?() do
assert pageview.site_id == site.id
else
assert pageview.domain == site.domain
end
assert pageview.pathname == "/"
end
test "works with Content-Type: text/plain", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "http://gigride.live/"
}
conn =
conn
|> put_req_header("content-type", "text/plain")
|> post("/api/event", Jason.encode!(params))
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.hostname == "gigride.live"
if Plausible.v2?() do
assert pageview.site_id == site.id
else
assert pageview.domain == site.domain
end
assert pageview.pathname == "/"
end
test "returns error if JSON cannot be parsed", %{conn: conn} do
conn =
conn
|> put_req_header("content-type", "text/plain")
|> post("/api/event", "")
assert conn.status == 400
end
test "can send to multiple dashboards by listing multiple domains - same timestamp", %{
conn: conn,
site: site1
} do
site2 = insert(:site)
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "http://m.facebook.com/",
domain: "#{site1.domain},#{site2.domain}"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
assert response(conn, 202) == "ok"
assert e1 = get_event(site1)
assert e2 = get_event(site2)
assert NaiveDateTime.compare(e1.timestamp, e2.timestamp) == :eq
end
test "timestamps differ when two events sent in a row", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "http://gigride.live/",
referrer: "http://m.facebook.com/"
}
t1 = System.convert_time_unit(System.monotonic_time(), :native, :millisecond)
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
t2 = System.convert_time_unit(System.monotonic_time(), :native, :millisecond)
# timestamps are in second precision, so we need to wait for it to flip
Process.sleep(1000 - (t2 - t1))
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
[e1, e2] = get_events(site)
assert NaiveDateTime.compare(e1.timestamp, e2.timestamp) == :gt
end
test "www. is stripped from domain", %{conn: conn, site: site} do
params = %{
name: "custom event",
url: "http://gigride.live/",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
if Plausible.v2?() do
assert pageview.site_id == site.id
else
assert pageview.domain == site.domain
end
end
test "www. is stripped from hostname", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.hostname == "example.com"
end
test "empty path defaults to /", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/"
end
test "trailing whitespace is removed", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/path ",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/path"
end
test "bots and crawlers are ignored", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/",
domain: site.domain
}
conn
|> put_req_header("user-agent", "generic crawler")
|> post("/api/event", params)
assert get_event(site) == nil
end
test "Headless Chrome is ignored", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/",
domain: site.domain
}
conn
|> put_req_header(
"user-agent",
"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/85.0.4183.83 Safari/537.36"
)
|> post("/api/event", params)
assert get_event(site) == nil
end
test "parses user_agent", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.operating_system == "Mac"
assert pageview.operating_system_version == "10.13"
assert pageview.browser == "Chrome"
assert pageview.browser_version == "70.0"
end
test "parses referrer", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https://facebook.com",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == "Facebook"
end
test "strips trailing slash from referrer", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https://facebook.com/page/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer == "facebook.com/page"
assert pageview.referrer_source == "Facebook"
end
test "ignores event when referrer is a spammer", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "http://gigride.live/",
referrer: "https://www.1-best-seo.com"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
assert response(conn, 202) == "ok"
assert !get_event(site)
end
test "blocks traffic from a domain when it's blocked", %{
conn: conn
} do
site = insert(:site, ingest_rate_limit_threshold: 0)
params = %{
domain: site.domain,
name: "pageview",
url: "https://feature-flag-test.com"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
assert response(conn, 202) == "ok"
refute get_event(site)
end
test "ignores when referrer is internal", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https://gigride.live",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == ""
end
test "ignores localhost referrer", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "http://localhost:4000/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == ""
end
test "parses subdomain referrer", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https://blog.gigride.live",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == "blog.gigride.live"
end
test "referrer is cleaned", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/",
referrer: "https://www.indiehackers.com/page?query=param#hash",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.referrer == "indiehackers.com/page"
end
test "utm_source overrides referrer source", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://www.example.com/?utm_source=betalist",
referrer: "https://betalist.com/my-produxct",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.referrer_source == "betalist"
end
test "utm tags are stored", %{conn: conn, site: site} do
params = %{
name: "pageview",
url:
"http://www.example.com/?utm_medium=ads&utm_source=instagram&utm_campaign=video_story",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.utm_medium == "ads"
assert pageview.utm_source == "instagram"
assert pageview.utm_campaign == "video_story"
end
test "if it's an :unknown referrer, just the domain is used", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https://www.indiehackers.com/landing-page-feedback",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == "indiehackers.com"
end
test "if the referrer is not http or https, it is ignored", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "android-app://com.google.android.gm",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.referrer_source == ""
end
test "screen size is calculated from user agent", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent_mobile)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.screen_size == "Mobile"
end
test "screen size is nil if user agent is unknown", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", "unknown UA")
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.screen_size == ""
end
test "screen size is calculated from user_agent when is tablet", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent_tablet)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.screen_size == "Tablet"
end
test "screen size is calculated from user_agent when is desktop", %{
conn: conn,
site: site
} do
params = %{
name: "pageview",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.screen_size == "Desktop"
end
test "can trigger a custom event", %{conn: conn, site: site} do
params = %{
name: "custom event",
url: "http://gigride.live/",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
event = get_event(site)
assert response(conn, 202) == "ok"
assert event.name == "custom event"
end
test "casts custom props to string", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: %{
bool_test: true,
number_test: 12
}
}
conn
|> post("/api/event", params)
event = get_event(site)
assert Map.get(event, :"meta.key") == ["bool_test", "number_test"]
assert Map.get(event, :"meta.value") == ["true", "12"]
end
test "ignores malformed custom props", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: "\"show-more:button\""
}
conn
|> post("/api/event", params)
event = get_event(site)
assert Map.get(event, :"meta.key") == []
assert Map.get(event, :"meta.value") == []
end
test "can send props stringified", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: Jason.encode!(%{number_test: 12})
}
conn
|> post("/api/event", params)
event = get_event(site)
assert Map.get(event, :"meta.key") == ["number_test"]
assert Map.get(event, :"meta.value") == ["12"]
end
test "ignores custom prop with array value", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: Jason.encode!(%{wat: ["some-thing"], other: "key"})
}
conn = post(conn, "/api/event", params)
assert conn.status == 202
event = get_event(site)
assert Map.get(event, :"meta.key") == ["other"]
assert Map.get(event, :"meta.value") == ["key"]
end
test "ignores custom prop with map value", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: Jason.encode!(%{foo: %{bar: "baz"}, other_key: 1})
}
conn = post(conn, "/api/event", params)
assert conn.status == 202
event = get_event(site)
assert Map.get(event, :"meta.key") == ["other_key"]
assert Map.get(event, :"meta.value") == ["1"]
end
test "ignores custom prop with empty string value", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: Jason.encode!(%{foo: "", other_key: true})
}
conn = post(conn, "/api/event", params)
assert conn.status == 202
event = get_event(site)
assert Map.get(event, :"meta.key") == ["other_key"]
assert Map.get(event, :"meta.value") == ["true"]
end
test "ignores custom prop with nil value", %{conn: conn, site: site} do
params = %{
name: "Signup",
url: "http://gigride.live/",
domain: site.domain,
props: Jason.encode!(%{foo: nil, other_key: true})
}
conn = post(conn, "/api/event", params)
assert conn.status == 202
event = get_event(site)
assert Map.get(event, :"meta.key") == ["other_key"]
assert Map.get(event, :"meta.value") == ["true"]
end
test "ignores a malformed referrer URL", %{conn: conn, site: site} do
params = %{
name: "pageview",
url: "http://gigride.live/",
referrer: "https:://twitter.com",
domain: site.domain
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
event = get_event(site)
assert response(conn, 202) == "ok"
assert event.referrer == ""
end
# Fake geo is loaded from test/priv/GeoLite2-City-Test.mmdb
test "looks up location data from the ip address", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "2.125.160.216")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "GB"
assert pageview.subdivision1_code == "GB-ENG"
assert pageview.subdivision2_code == "GB-WBK"
assert pageview.city_geoname_id == 2_655_045
end
test "ignores unknown country code ZZ", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "0.0.0.0")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == <<0, 0>>
assert pageview.subdivision1_code == ""
assert pageview.subdivision2_code == ""
assert pageview.city_geoname_id == 0
end
test "ignores disputed territory code XX", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "0.0.0.1")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == <<0, 0>>
assert pageview.subdivision1_code == ""
assert pageview.subdivision2_code == ""
assert pageview.city_geoname_id == 0
end
test "ignores TOR exit node country code T1", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "0.0.0.2")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == <<0, 0>>
assert pageview.subdivision1_code == ""
assert pageview.subdivision2_code == ""
assert pageview.city_geoname_id == 0
end
test "scrubs port from x-forwarded-for", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "216.160.83.56:123")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "US"
end
test "works with ipv6 without port in x-forwarded-for", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "2001:218:1:1:1:1:1:1")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "JP"
end
test "works with ipv6 with a port number in x-forwarded-for", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "[2001:218:1:1:1:1:1:1]:123")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "JP"
end
test "uses cloudflare's special header for client IP address if present", %{
conn: conn,
site: site
} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "0.0.0.0")
|> put_req_header("cf-connecting-ip", "216.160.83.56")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "US"
end
test "uses BunnyCDN's custom header for client IP address if present", %{
conn: conn,
site: site
} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("x-forwarded-for", "0.0.0.0")
|> put_req_header("b-forwarded-for", "216.160.83.56,9.9.9.9")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "US"
end
test "Uses the Forwarded header when cf-connecting-ip and x-forwarded-for are missing", %{
conn: conn,
site: site
} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header("forwarded", "by=0.0.0.0;for=216.160.83.56;host=somehost.com;proto=https")
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "US"
end
test "Forwarded header can parse ipv6", %{conn: conn, site: site} do
params = %{
name: "pageview",
domain: site.domain,
url: "http://gigride.live/"
}
conn
|> put_req_header(
"forwarded",
"by=0.0.0.0;for=\"[2001:218:1:1:1:1:1:1]\",for=0.0.0.0;host=somehost.com;proto=https"
)
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.country_code == "JP"
end
test "URL is decoded", %{conn: conn, site: site} do
params = %{
name: "pageview",
url:
"http://www.example.com/opportunity/category/%D8%AC%D9%88%D8%A7%D8%A6%D8%B2-%D9%88%D9%85%D8%B3%D8%A7%D8%A8%D9%82%D8%A7%D8%AA",
domain: site.domain
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/opportunity/category/جوائز-ومسابقات"
end
test "accepts shorthand map keys", %{conn: conn, site: site} do
params = %{
n: "pageview",
u: "http://www.example.com/opportunity",
d: site.domain,
r: "https://facebook.com/page"
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/opportunity"
assert pageview.referrer_source == "Facebook"
assert pageview.referrer == "facebook.com/page"
end
test "records hash when in hash mode", %{conn: conn, site: site} do
params = %{
n: "pageview",
u: "http://www.example.com/#page-a",
d: site.domain,
h: 1
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/#page-a"
end
test "does not record hash when hash mode is 0", %{conn: conn, site: site} do
params = %{
n: "pageview",
u: "http://www.example.com/#page-a",
d: site.domain,
h: 0
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.pathname == "/"
end
test "decodes URL pathname, fragment and search", %{conn: conn, site: site} do
params = %{
n: "pageview",
u:
"https://test.com/%EF%BA%9D%EF%BB%AD%EF%BA%8E%EF%BA%8B%EF%BA%AF-%EF%BB%AE%EF%BB%A4%EF%BA%B3%EF%BA%8E%EF%BA%92%EF%BB%97%EF%BA%8E%EF%BA%97?utm_source=%25balle%25",
d: site.domain,
h: 1
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.hostname == "test.com"
assert pageview.pathname == "/ﺝﻭﺎﺋﺯ-ﻮﻤﺳﺎﺒﻗﺎﺗ"
assert pageview.utm_source == "%balle%"
end
test "can use double quotes in query params", %{conn: conn, site: site} do
q = URI.encode_query(%{"utm_source" => "Something \"quoted\""})
params = %{
n: "pageview",
u: "https://test.com/?" <> q,
d: site.domain,
h: 1
}
conn
|> post("/api/event", params)
pageview = get_event(site)
assert pageview.utm_source == "Something \"quoted\""
end
test "responds 400 when required fields are missing", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
assert json_response(conn, 400) == %{
"errors" => %{
"url" => ["is required"]
}
}
end
test "responds 400 with errors when domain is missing", %{conn: conn} do
params = %{
domain: nil,
url: "about:config",
name: "pageview"
}
conn =
conn
|> put_req_header("user-agent", @user_agent)
|> post("/api/event", params)
assert json_response(conn, 400) == %{
"errors" => %{
"domain" => ["can't be blank"]
}
}
end
end
describe "user_id generation" do
setup do
site = insert(:site)
{:ok, site: site}
end
test "with same IP address and user agent, the same user ID is generated", %{
conn: conn,
site: site
} do
params = %{
url: "https://user-id-test-domain.com/",
domain: site.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
[one, two] = get_events(site)
assert one.user_id == two.user_id
end
test "different IP address results in different user ID", %{conn: conn, site: site} do
params = %{
url: "https://user-id-test-domain.com/",
domain: site.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "82.32.12.1")
|> post("/api/event", params)
[one, two] = get_events(site)
assert one.user_id != two.user_id
end
test "different user agent results in different user ID", %{conn: conn, site: site} do
params = %{
url: "https://user-id-test-domain.com/",
domain: site.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent <> "!!")
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
[one, two] = get_events(site)
assert one.user_id != two.user_id
end
test "different domain value results in different user ID", %{conn: conn, site: site1} do
site2 = insert(:site)
params = %{
url: "https://user-id-test-domain.com/",
domain: site1.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", Map.put(params, :domain, site2.domain))
one = get_event(site1)
two = get_event(site2)
assert one.user_id != two.user_id
end
test "different hostname results in different user ID", %{conn: conn, site: site} do
params = %{
url: "https://user-id-test-domain.com/",
domain: site.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", Map.put(params, :url, "https://other-domain.com/"))
[one, two] = get_events(site)
assert one.user_id != two.user_id
end
test "different hostname results in the same user ID when the root domain in the same", %{
conn: conn,
site: site
} do
params = %{
url: "https://user-id-test-domain.com/",
domain: site.domain,
name: "pageview"
}
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", params)
conn
|> put_req_header("user-agent", @user_agent)
|> put_req_header("x-forwarded-for", "127.0.0.1")
|> post("/api/event", Map.put(params, :url, "https://app.user-id-test-domain.com/"))
[one, two] = get_events(site)
assert one.user_id == two.user_id
end
end
describe "remaining" do
setup do
site = insert(:site)
{:ok, site: site}
end
test "defaults hostname to (none) when missing", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "file:///android_asset/www/index.html"
}
conn =
conn
|> put_req_header("content-type", "text/plain")
|> post("/api/event", Jason.encode!(params))
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.hostname == "(none)"
end
test "accepts chrome extension URLs", %{conn: conn, site: site} do
params = %{
domain: site.domain,
name: "pageview",
url: "chrome-extension://liipgellkffekalgefpjolodblggkmjg/popup.html"
}
conn =
conn
|> put_req_header("content-type", "text/plain")
|> post("/api/event", Jason.encode!(params))
pageview = get_event(site)
assert response(conn, 202) == "ok"
assert pageview.hostname == "liipgellkffekalgefpjolodblggkmjg"
end
end
describe "GET /api/health" do
test "returns 200 OK", %{conn: conn} do
conn = get(conn, "/api/health")
assert payload = json_response(conn, 200)
assert payload["postgres"] == "ok"
assert payload["clickhouse"] == "ok"
assert payload["sites_cache"] == "ok"
end
end
defp get_event(site) do
Plausible.Event.WriteBuffer.flush()
if Plausible.v2?() do
ClickhouseRepo.one(
from(e in Plausible.ClickhouseEventV2,
where: e.site_id == ^site.id,
order_by: [desc: e.timestamp]
)
)
else
ClickhouseRepo.one(
from(e in Plausible.ClickhouseEvent,
where: e.domain == ^site.domain,
order_by: [desc: e.timestamp]
)
)
end
end
defp get_events(site) do
Plausible.Event.WriteBuffer.flush()
if Plausible.v2?() do
ClickhouseRepo.all(
from(e in Plausible.ClickhouseEventV2,
where: e.site_id == ^site.id,
order_by: [desc: e.timestamp]
)
)
else
ClickhouseRepo.all(
from(e in Plausible.ClickhouseEvent,
where: e.domain == ^site.domain,
order_by: [desc: e.timestamp]
)
)
end
end
end