| 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 = new_site() |
| {:ok, site: site} |
| end |
|
|
| test "records the event and session", %{conn: conn, site: site} do |
| params = %{ |
| domain: site.domain, |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "http://m.facebook.com/" |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| pageview = get_event(site) |
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert pageview.hostname == "example.com" |
|
|
| assert pageview.site_id == site.id |
|
|
| assert pageview.pathname == "/" |
| assert pageview.session_id == session.session_id |
| end |
|
|
| test "works with Content-Type: text/plain", %{conn: conn, site: site} do |
| params = %{ |
| domain: site.domain, |
| name: "pageview", |
| url: "http://example.com/" |
| } |
|
|
| 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 == "example.com" |
|
|
| assert pageview.site_id == site.id |
|
|
| 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 = new_site() |
|
|
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| 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 |
|
|
| @tag :slow |
| test "timestamps differ when two events sent in a row", %{conn: conn, site: site} do |
| params = %{ |
| domain: site.domain, |
| name: "pageview", |
| url: "http://example.com/", |
| 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) |
|
|
| |
| 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://example.com/", |
| domain: site.domain |
| } |
|
|
| conn |
| |> post("/api/event", params) |
|
|
| pageview = get_event(site) |
|
|
| assert pageview.site_id == site.id |
| 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://example.com/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
| event = get_event(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.operating_system == "Mac" |
| assert session.operating_system_version == "10.13" |
| assert session.browser == "Chrome" |
| assert session.browser_version == "70.0" |
| assert event.operating_system == "Mac" |
| assert event.operating_system_version == "10.13" |
| assert event.browser == "Chrome" |
| assert event.browser_version == "70.0" |
| end |
|
|
| test "parses referrer source", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "https://facebook.com", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "Facebook" |
| assert session.click_id_param == "" |
| end |
|
|
| test "strips trailing slash from referrer", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "https://facebook.com/page/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
| event = get_event(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer == "facebook.com/page" |
| assert session.referrer_source == "Facebook" |
| assert event.referrer == session.referrer |
| assert event.referrer_source == session.referrer_source |
| end |
|
|
| test "ignores event when referrer is a spammer", %{conn: conn, site: site} do |
| params = %{ |
| domain: site.domain, |
| name: "pageview", |
| url: "http://example.com/", |
| 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 = new_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://example.com/", |
| referrer: "https://example.com", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "" |
| assert session.referrer == "" |
| end |
|
|
| test "ignores localhost referrer", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "http://localhost:4000/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "" |
| end |
|
|
| test "parses subdomain referrer", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "https://blog.example.com", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "blog.example.com" |
| 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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.referrer_source == "betalist" |
| assert session.utm_source == "betalist" |
| end |
|
|
| test "?ref param behaves like ?utm_source", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://www.example.com/?ref=betalist", |
| domain: site.domain |
| } |
|
|
| conn |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.referrer_source == "betalist" |
| assert session.utm_source == "betalist" |
| end |
|
|
| test "?source param behaves like ?utm_source", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://www.example.com/?source=betalist", |
| domain: site.domain |
| } |
|
|
| conn |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.referrer_source == "betalist" |
| assert session.utm_source == "betalist" |
| end |
|
|
| test "if utm_source matches a capitalized form from ref_inspector, the capitalized form is recorded", |
| %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://www.example.com/?utm_source=facebook", |
| domain: site.domain |
| } |
|
|
| conn |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.referrer_source == "Facebook" |
| 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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.utm_medium == "ads" |
| assert session.utm_source == "instagram" |
| assert session.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://example.com/", |
| referrer: "https://www.indiehackers.com/landing-page-feedback", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "indiehackers.com" |
| end |
|
|
| test "if the referrer is not http, https, or android it is ignored", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "ftp://wat", |
| 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 == "" |
| assert pageview.referrer == "" |
| end |
|
|
| test "stores referrer from android app", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "android-app://some.android.app", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer == "android-app://some.android.app" |
| assert session.referrer_source == "android-app://some.android.app" |
| end |
|
|
| test "screen size is calculated from user agent", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent_mobile) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.screen_size == "Mobile" |
| end |
|
|
| test "screen size is nil if user agent is unknown", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", "unknown UA") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.screen_size == "" |
| end |
|
|
| test "screen size is calculated from user_agent when is tablet", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent_tablet) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.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://example.com/", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.screen_size == "Desktop" |
| end |
|
|
| test "can trigger a custom event", %{conn: conn, site: site} do |
| params = %{ |
| name: "custom event", |
| url: "http://example.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.name == "custom event" |
| end |
|
|
| test "casts custom props to string", %{conn: conn, site: site} do |
| params = %{ |
| name: "Signup", |
| url: "http://example.com/", |
| 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 "records custom props for a engagement event", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{ |
| n: "pageview", |
| u: "https://ab.cd", |
| d: site.domain |
| }) |
|
|
| post(conn, "/api/event", %{ |
| name: "engagement", |
| url: "http://ab.cd/", |
| domain: site.domain, |
| sd: 50, |
| e: 1000, |
| props: %{ |
| bool_test: true, |
| number_test: 12 |
| } |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert Map.get(engagement, :"meta.key") == ["bool_test", "number_test"] |
| assert Map.get(engagement, :"meta.value") == ["true", "12"] |
| end |
|
|
| test "filters out bad props", %{conn: conn, site: site} do |
| params = %{ |
| name: "Signup", |
| url: "http://example.com/", |
| domain: site.domain, |
| props: %{ |
| false: nil, |
| nil: false, |
| good: true, |
| " ": " ", |
| " ": "value", |
| key: " " |
| } |
| } |
|
|
| conn |
| |> post("/api/event", params) |
|
|
| event = get_event(site) |
|
|
| assert Map.get(event, :"meta.key") == ["good"] |
| assert Map.get(event, :"meta.value") == ["true"] |
| end |
|
|
| test "ignores malformed custom props", %{conn: conn, site: site} do |
| params = %{ |
| name: "Signup", |
| url: "http://example.com/", |
| 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://example.com/", |
| 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://example.com/", |
| 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://example.com/", |
| 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://example.com/", |
| 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://example.com/", |
| 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 |
|
|
| @tag :ee_only |
| test "converts revenue values into the goal currency", %{conn: conn, site: site} do |
| params = %{ |
| name: "Payment", |
| url: "http://example.com/", |
| domain: site.domain, |
| revenue: %{amount: 10.2, currency: "USD"} |
| } |
|
|
| insert(:goal, event_name: "Payment", currency: "BRL", site: site) |
|
|
| assert %{status: 202} = post(conn, "/api/event", params) |
| assert %{revenue_reporting_amount: amount} = get_event(site) |
|
|
| assert Decimal.equal?(Decimal.new("7.14"), amount) |
| end |
|
|
| @tag :ee_only |
| test "revenue values can be sent with minified keys", %{conn: conn, site: site} do |
| params = %{ |
| "n" => "Payment", |
| "u" => "http://example.com/", |
| "d" => site.domain, |
| "$" => Jason.encode!(%{amount: 10.2, currency: "USD"}) |
| } |
|
|
| insert(:goal, event_name: "Payment", currency: "BRL", site: site) |
|
|
| assert %{status: 202} = post(conn, "/api/event", params) |
| assert %{revenue_reporting_amount: amount} = get_event(site) |
|
|
| assert Decimal.equal?(Decimal.new("7.14"), amount) |
| end |
|
|
| @tag :ee_only |
| test "saves the exact same amount when goal currency is the same as the event", %{ |
| conn: conn, |
| site: site |
| } do |
| params = %{ |
| name: "Payment", |
| url: "http://example.com/", |
| domain: site.domain, |
| revenue: %{amount: 10, currency: "BRL"} |
| } |
|
|
| insert(:goal, event_name: "Payment", currency: "BRL", site: site) |
|
|
| assert %{status: 202} = post(conn, "/api/event", params) |
| assert %{revenue_reporting_amount: amount} = get_event(site) |
|
|
| assert Decimal.equal?(Decimal.new("10.0"), amount) |
| end |
|
|
| test "does not fail when revenue value is invalid", %{conn: conn, site: site} do |
| params = %{ |
| name: "Payment", |
| url: "http://example.com/", |
| domain: site.domain, |
| revenue: %{amount: "1831d", currency: "ADSIE"} |
| } |
|
|
| insert(:goal, event_name: "Payment", currency: "BRL", site: site) |
|
|
| assert %{status: 202} = post(conn, "/api/event", params) |
| assert %Plausible.ClickhouseEventV2{} = get_event(site) |
| end |
|
|
| test "does not fail when sending revenue without a matching goal", %{conn: conn, site: site} do |
| params = %{ |
| name: "Add to Cart", |
| url: "http://example.com/", |
| domain: site.domain, |
| revenue: %{amount: 10.2, currency: "USD"} |
| } |
|
|
| insert(:goal, event_name: "Checkout", currency: "BRL", site: site) |
| insert(:goal, event_name: "Payment", currency: "USD", site: site) |
|
|
| assert %{status: 202} = post(conn, "/api/event", params) |
| assert %Plausible.ClickhouseEventV2{revenue_reporting_amount: nil} = get_event(site) |
| end |
|
|
| test "ignores a malformed referrer URL", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com/", |
| referrer: "https:://twitter.com", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer == "" |
| end |
|
|
| |
| test "looks up location data from the ip address", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "2.125.160.216") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
| event = get_event(site) |
|
|
| assert session.country_code == "GB" |
| assert session.subdivision1_code == "GB-ENG" |
| assert session.subdivision2_code == "GB-WBK" |
| assert session.city_geoname_id == 2_655_045 |
| assert event.country_code == session.country_code |
| assert event.subdivision1_code == session.subdivision1_code |
| assert event.subdivision2_code == session.subdivision2_code |
| assert event.city_geoname_id == session.city_geoname_id |
| end |
|
|
| test "ignores unknown country code ZZ", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "0.0.0.0") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == <<0, 0>> |
| assert session.subdivision1_code == "" |
| assert session.subdivision2_code == "" |
| assert session.city_geoname_id == 0 |
| end |
|
|
| test "ignores disputed territory code XX", %{conn: conn, site: site} do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "0.0.0.1") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == <<0, 0>> |
| assert session.subdivision1_code == "" |
| assert session.subdivision2_code == "" |
| assert session.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://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "0.0.0.2") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == <<0, 0>> |
| assert session.subdivision1_code == "" |
| assert session.subdivision2_code == "" |
| assert session.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://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "216.160.83.56:123") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "2001:218:1:1:1:1:1:1") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("x-forwarded-for", "[2001:218:1:1:1:1:1:1]:123") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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://example.com/" |
| } |
|
|
| 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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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://example.com/" |
| } |
|
|
| 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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == "US" |
| end |
|
|
| test "prioritizes x-plausible-ip header over everything else", %{ |
| conn: conn, |
| site: site |
| } do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| conn |
| |> put_req_header("cf-connecting-ip", "0.0.0.0") |
| |> put_req_header("b-forwarded-for", "0.0.0.0") |
| |> put_req_header("x-forwarded-for", "0.0.0.0") |
| |> put_req_header("forwarded", "for=0.0.0.0;host=dashboard.site.com;proto=https") |
| |> put_req_header("x-plausible-ip", "216.160.83.56") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == "US" |
| end |
|
|
| test "Uses the Forwarded header when cf-connecting-ip and x-forwarded-for are missing", %{ |
| site: site |
| } do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| build_conn() |
| |> put_req_header("forwarded", "by=0.0.0.0;for=216.160.83.56;host=somehost.com;proto=https") |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.country_code == "US" |
| end |
|
|
| test "Forwarded header can parse ipv6", %{site: site} do |
| params = %{ |
| name: "pageview", |
| domain: site.domain, |
| url: "http://example.com/" |
| } |
|
|
| build_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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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) |
| [session] = get_sessions(site) |
|
|
| assert pageview.pathname == "/opportunity" |
| assert session.referrer_source == "Facebook" |
| assert session.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) |
| [session] = get_sessions(site) |
|
|
| assert pageview.hostname == "test.com" |
| assert pageview.pathname == "/ﺝﻭﺎﺋﺯ-ﻮﻤﺳﺎﺒﻗﺎﺗ" |
| assert session.utm_source == "%balle%" |
| assert pageview.utm_source == session.utm_source |
| 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) |
|
|
| [session] = get_sessions(site) |
|
|
| assert session.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 when event name is blank", %{conn: conn, site: site} do |
| params = %{ |
| domain: site.domain, |
| name: "", |
| url: "http://example.com" |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| assert json_response(conn, 400) == %{ |
| "errors" => %{ |
| "event_name" => ["can't be blank"] |
| } |
| } |
| end |
|
|
| test "salts rotating once does not", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
| Plausible.Session.WriteBuffer.flush() |
| Plausible.Session.Salts.rotate() |
|
|
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| [event1, event2] = get_events(site) |
| [session1, session2] = get_sessions(site) |
|
|
| records = [event1, event2, session1, session2] |
|
|
| assert records |> Enum.map(& &1.user_id) |> Enum.uniq() |> Enum.count() == 1 |
| assert records |> Enum.map(& &1.session_id) |> Enum.uniq() |> Enum.count() == 1 |
| 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 "engagement event tests" do |
| setup do |
| site = new_site() |
| {:ok, site: site} |
| end |
|
|
| test "rejects engagement when both sd and e fields are missing", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| conn = post(conn, "/api/event", %{n: "engagement", u: "https://test.com", d: site.domain}) |
|
|
| assert %{"errors" => %{"event_name" => [error_msg]}} = json_response(conn, 400) |
|
|
| assert error_msg =~ |
| "engagement event requires a valid integer value for at least one of 'sd' or 'e' fields" |
| end |
|
|
| test "ingests scroll_depth as 255 when not in params", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
| post(conn, "/api/event", %{n: "engagement", u: "https://test.com", d: site.domain, e: 200}) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 255 |
| end |
|
|
| test "ingests engagement_time as 0 when not in params", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
| post(conn, "/api/event", %{n: "engagement", u: "https://test.com", d: site.domain, sd: 50}) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.engagement_time == 0 |
| end |
|
|
| test "ingests engagement_time as 0 when tracker is sending invalid high values", %{ |
| conn: conn, |
| site: site |
| } do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| sd: 50, |
| e: 1_741_850_224_785 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.engagement_time == 0 |
| assert engagement.scroll_depth == 50 |
| end |
|
|
| test "sd and e fields are ignored if name is not engagement", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{ |
| n: "pageview", |
| u: "https://test.com", |
| d: site.domain, |
| sd: 10, |
| e: 789 |
| }) |
|
|
| post(conn, "/api/event", %{ |
| n: "custom_e", |
| u: "https://test.com", |
| d: site.domain, |
| sd: 10, |
| e: 789 |
| }) |
|
|
| assert [%{scroll_depth: 0, engagement_time: 0}, %{scroll_depth: 0, engagement_time: 0}] = |
| get_events(site) |
| end |
|
|
| test "ingests valid scroll_depth and engagement_time for a engagement", %{ |
| conn: conn, |
| site: site |
| } do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| sd: 25, |
| e: 789 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 25 |
| assert engagement.engagement_time == 789 |
| end |
|
|
| test "ingests scroll_depth as 100 when sd > 100", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
| post(conn, "/api/event", %{n: "engagement", u: "https://test.com", d: site.domain, sd: 101}) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 100 |
| end |
|
|
| test "parses scroll_depth from a string", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
| post(conn, "/api/event", %{n: "engagement", u: "https://test.com", d: site.domain, sd: "1"}) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 1 |
| end |
|
|
| test "ingests scroll_depth as 255 when sd is a negative integer", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| sd: -1, |
| e: 100 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 255 |
| end |
|
|
| test "ingests scroll_depth as 255 when sd is a non-number string", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| sd: "12asd", |
| e: 100 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.scroll_depth == 255 |
| end |
|
|
| test "ingests engagement_time from a string", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| e: "789", |
| sd: 40 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.engagement_time == 789 |
| end |
|
|
| test "ingests engagement_time as 0 when e is a negative integer", %{conn: conn, site: site} do |
| post(conn, "/api/event", %{n: "pageview", u: "https://test.com", d: site.domain}) |
|
|
| post(conn, "/api/event", %{ |
| n: "engagement", |
| u: "https://test.com", |
| d: site.domain, |
| e: -100, |
| sd: 50 |
| }) |
|
|
| engagement = get_events(site) |> Enum.find(&(&1.name == "engagement")) |
|
|
| assert engagement.engagement_time == 0 |
| end |
| end |
|
|
| describe "acquisition channel tests" do |
| setup do |
| site = new_site() |
| {:ok, site: site} |
| end |
|
|
| test "parses cross network channel", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com/?utm_campaign=cross-network"}) |
| |> assert_acquisition_channel("Cross-network") |
| end |
|
|
| test "parses paid shopping channel based on campaign/medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com/?utm_campaign=shopping&utm_medium=paid"}) |
| |> assert_acquisition_channel("Paid Shopping") |
| end |
|
|
| test "parses paid shopping channel based on referrer source and medium", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?utm_medium=paid", |
| referrer: "https://shopify.com" |
| }) |
| |> assert_acquisition_channel("Paid Shopping") |
| end |
|
|
| test "parses paid shopping channel based on referrer utm_source and medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=shopify&utm_medium=paid"}) |
| |> assert_acquisition_channel("Paid Shopping") |
| end |
|
|
| test "parses paid search channel based on referrer and medium", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?utm_medium=paid", |
| referrer: "https://duckduckgo.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| end |
|
|
| test "parses paid search channel based on gclid", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?gclid=123identifier", |
| referrer: "https://google.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_utm_medium("(gclid)") |
| |> assert_click_id_param("gclid") |
| end |
|
|
| test "is not paid search when gclid is present on non-google referrer", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?gclid=123identifier", |
| referrer: "https://duckduckgo.com" |
| }) |
| |> assert_acquisition_channel("Organic Search") |
| |> assert_utm_medium("") |
| |> assert_click_id_param("gclid") |
| end |
|
|
| test "does not override utm_medium with (gclid) if link is already tagged", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?gclid=123identifier&utm_medium=paidads", |
| referrer: "https://google.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_utm_medium("paidads") |
| |> assert_click_id_param("gclid") |
| end |
|
|
| test "parses paid search channel based on msclkid", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?msclkid=123identifier", |
| referrer: "https://bing.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_utm_medium("(msclkid)") |
| |> assert_click_id_param("msclkid") |
| end |
|
|
| test "is not paid search when msclkid is present on non-bing referrer", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?msclkid=123identifier&utm_medium=cpc", |
| referrer: "https://bing.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_utm_medium("cpc") |
| |> assert_click_id_param("msclkid") |
| end |
|
|
| test "does not override utm_medium with (msclkid) if link is already tagged", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?gclid=123identifier&utm_medium=paidads", |
| referrer: "https://google.com" |
| }) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_utm_medium("paidads") |
| |> assert_click_id_param("gclid") |
| end |
|
|
| test "parses paid search channel based on utm_source and medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=google&utm_medium=paid"}) |
| |> assert_acquisition_channel("Paid Search") |
| |> assert_click_id_param("") |
| end |
|
|
| test "parses paid social channel based on referrer and medium", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?utm_medium=paid", |
| referrer: "https://tiktok.com" |
| }) |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "parses paid social channel based on utm_source and medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=tiktok&utm_medium=paid"}) |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "parses paid video channel based on referrer and medium", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?utm_medium=paid", |
| referrer: "https://youtube.com" |
| }) |
| |> assert_acquisition_channel("Paid Video") |
| end |
|
|
| test "parses paid video channel based on utm_source and medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=youtube&utm_medium=paid"}) |
| |> assert_acquisition_channel("Paid Video") |
| end |
|
|
| test "parses display channel", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=banner"}) |
| |> assert_acquisition_channel("Display") |
| end |
|
|
| test "display channel with gclid", %{site: site} do |
| site |
| |> submit_event(%{ |
| url: "http://example.com?utm_medium=display&utm_source=google&gclid=123identifier" |
| }) |
| |> assert_acquisition_channel("Display") |
| end |
|
|
| test "parses paid other channel", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=cpc"}) |
| |> assert_acquisition_channel("Paid Other") |
| end |
|
|
| test "parses organic shopping channel from referrer", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com", referrer: "https://walmart.com"}) |
| |> assert_acquisition_channel("Organic Shopping") |
| end |
|
|
| test "parses organic shopping channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=walmart"}) |
| |> assert_acquisition_channel("Organic Shopping") |
| end |
|
|
| test "parses organic shopping channel from utm_campaign", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_campaign=shop"}) |
| |> assert_acquisition_channel("Organic Shopping") |
| end |
|
|
| test "parses organic social channel from referrer", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com", referrer: "http://facebook.com"}) |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "parses organic social channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=twitter"}) |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "parses organic social channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=social"}) |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "parses organic video channel from referrer", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com", referrer: "https://vimeo.com"}) |
| |> assert_acquisition_channel("Organic Video") |
| end |
|
|
| test "parses organic video channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=vimeo"}) |
| |> assert_acquisition_channel("Organic Video") |
| end |
|
|
| test "parses organic video channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=video"}) |
| |> assert_acquisition_channel("Organic Video") |
| end |
|
|
| test "parses organic search channel from referrer", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com", referrer: "http://duckduckgo.com"}) |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "parses organic search channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=duckduckgo"}) |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "parses referral channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=referral"}) |
| |> assert_acquisition_channel("Referral") |
| end |
|
|
| test "parses email channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=email"}) |
| |> assert_acquisition_channel("Email") |
| end |
|
|
| test "parses email channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=email"}) |
| |> assert_acquisition_channel("Email") |
| end |
|
|
| test "parses affiliates channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=affiliate"}) |
| |> assert_acquisition_channel("Affiliates") |
| end |
|
|
| test "parses audio channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=audio"}) |
| |> assert_acquisition_channel("Audio") |
| end |
|
|
| test "parses sms channel from utm_source", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_source=sms"}) |
| |> assert_acquisition_channel("SMS") |
| end |
|
|
| test "parses sms channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=sms"}) |
| |> assert_acquisition_channel("SMS") |
| end |
|
|
| test "parses mobile push notifications channel from utm_medium with push", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=app-push"}) |
| |> assert_acquisition_channel("Mobile Push Notifications") |
| end |
|
|
| test "parses mobile push notifications channel from utm_medium", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com?utm_medium=example-mobile"}) |
| |> assert_acquisition_channel("Mobile Push Notifications") |
| end |
|
|
| test "parses referral channel if session starts with a simple referral", %{site: site} do |
| site |
| |> submit_event(%{url: "http://example.com", referrer: "https://othersite.com"}) |
| |> assert_acquisition_channel("Referral") |
| end |
|
|
| test "parses direct channel if session starts without referrer or utm tags", %{ |
| site: site |
| } do |
| site |
| |> submit_event(%{ |
| name: "pageview", |
| url: "http://example.com", |
| domain: site.domain |
| }) |
| |> assert_acquisition_channel("Direct") |
| end |
| end |
|
|
| describe "custom source parsing rules" do |
| setup do |
| site = new_site() |
| {:ok, site: site} |
| end |
|
|
| defp submit_event(site, params_overrides) do |
| params = |
| Map.merge( |
| %{name: "pageview", url: "http://example.com", domain: site.domain}, |
| params_overrides |
| ) |
|
|
| conn = |
| build_conn() |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| assert response(conn, 202) == "ok" |
|
|
| [session] = get_sessions(site) |
| session |
| end |
|
|
| defp event_with_referrer(site, referrer) do |
| submit_event(site, %{referrer: referrer}) |
| end |
|
|
| defp event_with_utm_source(site, utm_source) do |
| submit_event(site, %{url: "http://example.com?utm_source=#{utm_source}"}) |
| end |
|
|
| defp assert_source(session, expected) do |
| assert session.referrer_source == expected |
| session |
| end |
|
|
| defp assert_utm_source(session, expected) do |
| assert session.utm_source == expected |
| session |
| end |
|
|
| defp assert_utm_medium(session, expected) do |
| assert session.utm_medium == expected |
| session |
| end |
|
|
| defp assert_click_id_param(session, expected) do |
| assert session.click_id_param == expected |
| session |
| end |
|
|
| defp assert_acquisition_channel(session, expected) do |
| assert session.acquisition_channel == expected |
| session |
| end |
|
|
| test "threads is Threads", %{site: site} do |
| site |
| |> event_with_utm_source("threads") |
| |> assert_source("Threads") |
| |> assert_utm_source("threads") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "ig is Instagram", %{site: site} do |
| site |
| |> event_with_utm_source("ig") |
| |> assert_source("Instagram") |
| |> assert_utm_source("ig") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "yt is Youtube", %{site: site} do |
| site |
| |> event_with_utm_source("yt") |
| |> assert_source("Youtube") |
| |> assert_utm_source("yt") |
| |> assert_acquisition_channel("Organic Video") |
| end |
|
|
| test "yt-ads is Youtube paid", %{site: site} do |
| site |
| |> event_with_utm_source("yt-ads") |
| |> assert_source("Youtube") |
| |> assert_utm_source("yt-ads") |
| |> assert_acquisition_channel("Paid Video") |
| end |
|
|
| test "fb is Facebook", %{site: site} do |
| site |
| |> event_with_utm_source("fb") |
| |> assert_source("Facebook") |
| |> assert_utm_source("fb") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "fb-ads is Facebook", %{site: site} do |
| site |
| |> event_with_utm_source("fb-ads") |
| |> assert_source("Facebook") |
| |> assert_utm_source("fb-ads") |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "fbad is Facebook", %{site: site} do |
| site |
| |> event_with_utm_source("fbad") |
| |> assert_source("Facebook") |
| |> assert_utm_source("fbad") |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "facebook-ads is Facebook", %{site: site} do |
| site |
| |> event_with_utm_source("facebook-ads") |
| |> assert_source("Facebook") |
| |> assert_utm_source("facebook-ads") |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "Reddit-ads is Reddit", %{site: site} do |
| site |
| |> event_with_utm_source("Reddit-ads") |
| |> assert_source("Reddit") |
| |> assert_utm_source("Reddit-ads") |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "google_ads is Google", %{site: site} do |
| site |
| |> event_with_utm_source("google_ads") |
| |> assert_source("Google") |
| |> assert_utm_source("google_ads") |
| |> assert_acquisition_channel("Paid Search") |
| end |
|
|
| test "Google-ads is Google", %{ |
| site: site |
| } do |
| site |
| |> submit_event(%{url: "http://example.com?source=Google-ads"}) |
| |> assert_source("Google") |
| |> assert_utm_source("Google-ads") |
| |> assert_acquisition_channel("Paid Search") |
| end |
|
|
| test "utm_source=Adwords is Google paid search", %{site: site} do |
| site |
| |> event_with_utm_source("Adwords") |
| |> assert_source("Google") |
| |> assert_utm_source("Adwords") |
| |> assert_acquisition_channel("Paid Search") |
| end |
|
|
| test "twitter-ads is X (Twitter)", %{site: site} do |
| site |
| |> event_with_utm_source("twitter-ads") |
| |> assert_source("X (Twitter)") |
| |> assert_utm_source("twitter-ads") |
| |> assert_acquisition_channel("Paid Social") |
| end |
|
|
| test "android-app://com.reddit.frontpage is Reddit", %{site: site} do |
| site |
| |> event_with_referrer("android-app://com.reddit.frontpage") |
| |> assert_source("Reddit") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "perplexity.ai is Perplexity", %{site: site} do |
| site |
| |> event_with_referrer("https://perplexity.ai") |
| |> assert_source("Perplexity") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "pplx.ai is Perplexity", %{site: site} do |
| site |
| |> event_with_referrer("https://pplx.ai") |
| |> assert_source("Perplexity") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "utm_source=perplexity is Perplexity", %{site: site} do |
| site |
| |> event_with_utm_source("perplexity") |
| |> assert_source("Perplexity") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "statics.teams.cdn.office.net is Microsoft Teams", %{site: site} do |
| site |
| |> event_with_referrer("https://statics.teams.cdn.office.net") |
| |> assert_source("Microsoft Teams") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "wikipedia domain is resolved as Wikipedia", %{site: site} do |
| site |
| |> event_with_referrer("https://en.wikipedia.org") |
| |> assert_source("Wikipedia") |
| |> assert_acquisition_channel("Referral") |
| end |
|
|
| test "ntp.msn.com is Bing", %{site: site} do |
| site |
| |> event_with_referrer("https://ntp.msn.com") |
| |> assert_source("Bing") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "search.brave.com is Brave", %{site: site} do |
| site |
| |> event_with_referrer("https://search.brave.com") |
| |> assert_source("Brave") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.com.tr is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.com.tr") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.kz is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.kz") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "ya.ru is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://ya.ru") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.uz is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.uz") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.fr is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.fr") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.eu is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.eu") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "yandex.tm is Yandex", %{site: site} do |
| site |
| |> event_with_referrer("https://yandex.tm") |
| |> assert_source("Yandex") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "discord.com is Discord", %{site: site} do |
| site |
| |> event_with_referrer("https://discord.com") |
| |> assert_source("Discord") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "discordapp.com is Discord", %{site: site} do |
| site |
| |> event_with_referrer("https://discordapp.com") |
| |> assert_source("Discord") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "canary.discord.com is Discord", %{site: site} do |
| site |
| |> event_with_referrer("https://canary.discord.com") |
| |> assert_source("Discord") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "ptb.discord.com is Discord", %{site: site} do |
| site |
| |> event_with_referrer("https://ptb.discord.com") |
| |> assert_source("Discord") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "www.baidu.com is Baidu", %{site: site} do |
| site |
| |> event_with_referrer("https://baidu.com") |
| |> assert_source("Baidu") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "t.me is Telegram", %{site: site} do |
| site |
| |> event_with_referrer("https://t.me") |
| |> assert_source("Telegram") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "webk.telegram.org is Telegram", %{site: site} do |
| site |
| |> event_with_referrer("https://webk.telegram.org") |
| |> assert_source("Telegram") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "sogou.com is Sogou", %{site: site} do |
| site |
| |> event_with_referrer("https://sogou.com") |
| |> assert_source("Sogou") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "m.sogou.com is Sogou", %{site: site} do |
| site |
| |> event_with_referrer("https://m.sogou.com") |
| |> assert_source("Sogou") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "wap.sogou.com is Sogou", %{site: site} do |
| site |
| |> event_with_referrer("https://wap.sogou.com") |
| |> assert_source("Sogou") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "linktr.ee is Linktree", %{site: site} do |
| site |
| |> event_with_referrer("https://linktr.ee") |
| |> assert_source("Linktree") |
| |> assert_acquisition_channel("Referral") |
| end |
|
|
| test "linktree is Linktree", %{site: site} do |
| site |
| |> event_with_utm_source("linktree") |
| |> assert_source("Linktree") |
| |> assert_acquisition_channel("Referral") |
| end |
| end |
|
|
| describe "custom channel parsing rules" do |
| setup do |
| site = new_site() |
| {:ok, site: site} |
| end |
|
|
| test "hacker news is social channel", %{site: site} do |
| site |
| |> event_with_referrer("https://news.ycombinator.com") |
| |> assert_source("Hacker News") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "yahoo is organic search", %{site: site} do |
| site |
| |> event_with_referrer("https://search.yahoo.com") |
| |> assert_source("Yahoo!") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "gmail is email channel", %{site: site} do |
| site |
| |> event_with_referrer("https://mail.google.com") |
| |> assert_source("Gmail") |
| |> assert_acquisition_channel("Email") |
| end |
|
|
| test "utm_source=newsletter is email channel", %{site: site} do |
| site |
| |> event_with_utm_source("Newsletter-UK") |
| |> assert_source("Newsletter-UK") |
| |> assert_acquisition_channel("Email") |
| end |
|
|
| test "temu.com is shopping channel", %{site: site} do |
| site |
| |> event_with_referrer("https://temu.com") |
| |> assert_source("temu.com") |
| |> assert_acquisition_channel("Organic Shopping") |
| end |
|
|
| test "utm_source=Telegram is social channel", %{site: site} do |
| site |
| |> event_with_utm_source("Telegram") |
| |> assert_source("Telegram") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "Slack is social channel", %{site: site} do |
| site |
| |> event_with_referrer("https://app.slack.com") |
| |> assert_source("Slack") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "producthunt is social", %{ |
| conn: conn, |
| site: site |
| } do |
| params = %{ |
| name: "pageview", |
| url: "http://example.com?ref=producthunt", |
| domain: site.domain |
| } |
|
|
| conn = |
| conn |
| |> put_req_header("user-agent", @user_agent) |
| |> post("/api/event", params) |
|
|
| [session] = get_sessions(site) |
|
|
| assert response(conn, 202) == "ok" |
| assert session.referrer_source == "producthunt" |
| assert session.acquisition_channel == "Organic Social" |
| end |
|
|
| test "github is social", %{site: site} do |
| site |
| |> event_with_referrer("https://github.com") |
| |> assert_source("GitHub") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "steamcommunity.com is social", %{site: site} do |
| site |
| |> event_with_referrer("https://steamcommunity.com") |
| |> assert_source("steamcommunity.com") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "Vkontakte is social", %{site: site} do |
| site |
| |> event_with_referrer("https://vkontakte.ru") |
| |> assert_source("Vkontakte") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "Threads is social", %{site: site} do |
| site |
| |> event_with_referrer("https://threads.net") |
| |> assert_source("Threads") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "Ecosia is search", %{site: site} do |
| site |
| |> event_with_referrer("https://ecosia.org") |
| |> assert_source("Ecosia") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "bsky.app is Bluesky and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://bsky.app") |
| |> assert_source("Bluesky") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "go.bsky.app is Bluesky and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://go.bsky.app") |
| |> assert_source("Bluesky") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "mastodon.social is Mastodon and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://mastodon.social") |
| |> assert_source("Mastodon") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "fosstodon.org is Mastodon and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://fosstodon.org") |
| |> assert_source("Mastodon") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "gemini.google.com is Google Gemini and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://gemini.google.com") |
| |> assert_source("Google Gemini") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "chatgpt.com is ChatGPT and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://chatgpt.com") |
| |> assert_source("ChatGPT") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "chat.openai.com is ChatGPT and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://chat.openai.com") |
| |> assert_source("ChatGPT") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "claude.ai is Claude and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://claude.ai") |
| |> assert_source("Claude") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "phind.com is Phind and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://phind.com") |
| |> assert_source("Phind") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "copilot.microsoft.com is Microsoft Copilot and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://copilot.microsoft.com") |
| |> assert_source("Microsoft Copilot") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "copilot.com is Microsoft Copilot and AI Assistants", %{site: site} do |
| site |
| |> event_with_referrer("https://copilot.com") |
| |> assert_source("Microsoft Copilot") |
| |> assert_acquisition_channel("AI Assistants") |
| end |
|
|
| test "x.com is X (Twitter) and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://x.com") |
| |> assert_source("X (Twitter)") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "l.threads.com is Threads and Organic Social", %{site: site} do |
| site |
| |> event_with_referrer("https://l.threads.com") |
| |> assert_source("Threads") |
| |> assert_acquisition_channel("Organic Social") |
| end |
|
|
| test "kagi.com is Kagi and Organic Search", %{site: site} do |
| site |
| |> event_with_referrer("https://kagi.com") |
| |> assert_source("Kagi") |
| |> assert_acquisition_channel("Organic Search") |
| end |
|
|
| test "officeapps.live.com subdomains are Microsoft 365 and Referral", %{site: site} do |
| site |
| |> event_with_referrer("https://cac-excel.officeapps.live.com") |
| |> assert_source("Microsoft 365") |
| |> assert_acquisition_channel("Referral") |
| end |
| end |
|
|
| describe "user_id generation" do |
| setup do |
| site = new_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 = new_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 = new_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() |
|
|
| ClickhouseRepo.one( |
| from(e in Plausible.ClickhouseEventV2, |
| where: e.site_id == ^site.id, |
| order_by: [desc: e.timestamp] |
| ) |
| ) |
| end |
|
|
| defp get_sessions(site) do |
| Plausible.Session.WriteBuffer.flush() |
|
|
| ClickhouseRepo.all( |
| from(s in Plausible.ClickhouseSessionV2, |
| where: s.site_id == ^site.id and s.sign == 1, |
| order_by: [desc: s.timestamp] |
| ) |
| ) |
| end |
|
|
| defp get_events(site) do |
| Plausible.Event.WriteBuffer.flush() |
|
|
| ClickhouseRepo.all( |
| from(e in Plausible.ClickhouseEventV2, |
| where: e.site_id == ^site.id, |
| order_by: [desc: e.timestamp] |
| ) |
| ) |
| end |
| end |
|
|