Refactor Verification module structure (#5570)

* detector.js

* refactor: organize modules better

* Renaming (Elixir + JS)

* lib/plausible/verification -> lib/plausible/installation_support
* test/plausible/verification -> test/plausible/installation_support
* priv/tracker/verifier -> priv/tracker/installation_support
* tracker/verifier -> tracker/installation_support
* tracker/test/verifier -> tracker/test/installation-support

* rename remaining test modules

* add documentation

* dialyzer: remove module refs that do not exist yet

* Fix CI

* fix tracker CI

* fix tracker CI for good
This commit is contained in:
RobertJoonas 2025-07-15 13:50:34 +03:00 committed by GitHub
parent 39c006bfb9
commit 97dcc3fe7c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
62 changed files with 531 additions and 336 deletions

2
.gitignore vendored
View File

@ -92,7 +92,7 @@ plausible-report.xml
# Auto-generated tracker files
/priv/tracker/js/plausible*.js*
/priv/tracker/verifier/*.js
/priv/tracker/installation_support/*.js
# Docker volumes
.clickhouse_db_vol*

View File

@ -937,7 +937,7 @@ config :plausible, Plausible.PromEx,
grafana: :disabled,
metrics_server: :disabled
config :plausible, Plausible.Verification.Checks.Installation,
config :plausible, Plausible.InstallationSupport,
token: get_var_from_path_or_env(config_dir, "BROWSERLESS_TOKEN", "dummy_token"),
endpoint: get_var_from_path_or_env(config_dir, "BROWSERLESS_ENDPOINT", "http://0.0.0.0:3000")

View File

@ -34,14 +34,14 @@ config :plausible, Plausible.Ingestion.Counters, enabled: false
config :plausible, Oban, testing: :manual
config :plausible, Plausible.Verification.Checks.FetchBody,
config :plausible, Plausible.InstallationSupport.Checks.FetchBody,
req_opts: [
plug: {Req.Test, Plausible.Verification.Checks.FetchBody}
plug: {Req.Test, Plausible.InstallationSupport.Checks.FetchBody}
]
config :plausible, Plausible.Verification.Checks.Installation,
config :plausible, Plausible.InstallationSupport.Checks.Installation,
req_opts: [
plug: {Req.Test, Plausible.Verification.Checks.Installation}
plug: {Req.Test, Plausible.InstallationSupport.Checks.Installation}
]
config :plausible, Plausible.HelpScout,

View File

@ -21,7 +21,7 @@ defmodule Plausible.Ingestion.Event do
salts: nil,
changeset: nil
@verification_user_agent Plausible.Verification.user_agent()
@verification_user_agent Plausible.InstallationSupport.user_agent()
@type drop_reason() ::
:bot

View File

@ -0,0 +1,39 @@
defmodule Plausible.InstallationSupport.Check do
@moduledoc """
Behaviour to be implemented by a specific installation support check.
`report_progress_as()` doesn't necessarily reflect the actual check
description, it serves as a user-facing message grouping mechanism,
to prevent frequent message flashing when checks rotate often.
Each check operates on `%Plausible.InstallationSupport.State{}` and is
expected to return it, optionally modified, by all means. `perform_safe/1`
is used to guarantee no exceptions are thrown by faulty implementations,
not to interrupt LiveView.
"""
@type state() :: Plausible.InstallationSupport.State.t()
@callback report_progress_as() :: String.t()
@callback perform(state()) :: state()
defmacro __using__(_) do
quote do
import Plausible.InstallationSupport.State
alias Plausible.InstallationSupport.State
require Logger
@behaviour Plausible.InstallationSupport.Check
def perform_safe(state) do
perform(state)
catch
_, e ->
Logger.error(
"Error running check #{inspect(__MODULE__)} on #{state.url}: #{inspect(e)}"
)
put_diagnostics(state, service_error: e)
end
end
end
end

View File

@ -0,0 +1,60 @@
defmodule Plausible.InstallationSupport.CheckRunner do
@moduledoc """
Takes two arguments:
1. A `%Plausible.InstallationSupport.State{}` struct - the `diagnostics`
field is a struct representing the set of diagnostics shared between
all the checks in this flow.
2. A list of modules implementing `Plausible.InstallationSupport.Check`
behaviour.
Checks are normally run asynchronously, except when synchronous
execution is optionally required for tests. Slowdowns can be optionally
added, the user doesn't benefit from running the checks too quickly.
"""
def run(state, checks, opts) do
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
if async? do
Task.start_link(fn -> do_run(state, checks, slowdown) end)
else
do_run(state, checks, slowdown)
end
end
defp do_run(state, checks, slowdown) do
state =
Enum.reduce(
checks,
state,
fn check, state ->
state
|> notify_check_start(check, slowdown)
|> check.perform_safe()
end
)
notify_all_checks_done(state, slowdown)
end
defp notify_check_start(state, check, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:check_start, {check, state}})
end
state
end
defp notify_all_checks_done(state, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:all_checks_done, state})
end
state
end
end

View File

@ -1,9 +1,9 @@
defmodule Plausible.Verification.Checks.CSP do
defmodule Plausible.InstallationSupport.Checks.CSP do
@moduledoc """
Scans the Content Security Policy header to ensure that the Plausible domain is allowed.
See `Plausible.Verification.Checks` for the execution sequence.
See `Plausible.InstallationSupport.LegacyVerification.Checks` for the execution sequence.
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working"

View File

@ -1,15 +1,16 @@
defmodule Plausible.Verification.Checks.FetchBody do
defmodule Plausible.InstallationSupport.Checks.FetchBody do
@moduledoc """
Fetches the body of the site and extracts the HTML document, if available, for
further processing.
See `Plausible.Verification.Checks` for the execution sequence.
further processing. See `Plausible.InstallationSupport.LegacyVerification.Checks`
for the execution sequence.
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working"
@impl true
def perform(%State{url: "https://" <> _ = url} = state) do
fetch_body_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []

View File

@ -1,12 +1,12 @@
defmodule Plausible.Verification.Checks.Installation do
defmodule Plausible.InstallationSupport.Checks.Installation do
require Logger
path = Application.app_dir(:plausible, "priv/tracker/verifier/verifier-v1.js")
path = Application.app_dir(:plausible, "priv/tracker/installation_support/verifier-v1.js")
# On CI, the file might not be present for static checks so we create an empty one
File.touch!(path)
@verifier_code File.read!(path)
@external_resource "priv/tracker/verifier/verifier-v1.js"
@external_resource "priv/tracker/installation_support/verifier-v1.js"
# Puppeteer wrapper function that executes the vanilla JS verifier code
@puppeteer_wrapper_code """
@ -45,7 +45,7 @@ defmodule Plausible.Verification.Checks.Installation do
- `data.callbackStatus` - integer. 202 indicates that the server acknowledged the test event.
The test event ingestion is discarded based on user-agent, see:
`Plausible.Verification.user_agent/0`
`Plausible.InstallationSupport.user_agent/0`
- `data.dataDomainMismatch` - whether or not script[data-domain] mismatched with site.domain
@ -63,7 +63,7 @@ defmodule Plausible.Verification.Checks.Installation do
- `data.cookieBannerLikely` - whether or not there's a cookie banner blocking Plausible
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're verifying that your visitors are being counted correctly"
@ -77,8 +77,8 @@ defmodule Plausible.Verification.Checks.Installation do
code: @puppeteer_wrapper_code,
context: %{
expectedDataDomain: data_domain,
url: Plausible.Verification.URL.bust_url(url),
userAgent: Plausible.Verification.user_agent(),
url: Plausible.InstallationSupport.URL.bust_url(url),
userAgent: Plausible.InstallationSupport.user_agent(),
debug: Application.get_env(:plausible, :environment) == "dev"
}
}),
@ -90,7 +90,7 @@ defmodule Plausible.Verification.Checks.Installation do
extra_opts = Application.get_env(:plausible, __MODULE__)[:req_opts] || []
opts = Keyword.merge(opts, extra_opts)
case Req.post(verification_endpoint(), opts) do
case Req.post(Plausible.InstallationSupport.browserless_function_api_endpoint(), opts) do
{:ok, %{status: 200, body: %{"data" => %{"completed" => true} = js_data}}} ->
emit_telemetry_and_log(state.diagnostics, js_data, data_domain)
@ -159,11 +159,4 @@ defmodule Plausible.Verification.Checks.Installation do
:telemetry.execute(telemetry_event(any_diff?), %{})
end
defp verification_endpoint() do
config = Application.fetch_env!(:plausible, __MODULE__)
token = Keyword.fetch!(config, :token)
endpoint = Keyword.fetch!(config, :endpoint)
Path.join(endpoint, "function?token=#{token}&stealth")
end
end

View File

@ -1,8 +1,8 @@
defmodule Plausible.Verification.Checks.ScanBody do
defmodule Plausible.InstallationSupport.Checks.ScanBody do
@moduledoc """
Naive way of detecting GTM and WordPress powered sites.
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're visiting your site to ensure that everything is working"

View File

@ -1,10 +1,10 @@
defmodule Plausible.Verification.Checks.Snippet do
defmodule Plausible.InstallationSupport.Checks.Snippet do
@moduledoc """
The check looks for Plausible snippets and tries to address the common
integration issues, such as bad placement, data-domain typos, unknown
attributes frequently added by performance optimization plugins, etc.
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "We're looking for the Plausible snippet on your site"

View File

@ -1,12 +1,15 @@
defmodule Plausible.Verification.Checks.SnippetCacheBust do
defmodule Plausible.InstallationSupport.Checks.SnippetCacheBust do
@moduledoc """
A naive way of trying to figure out whether the latest site contents
is wrapped with some CDN/caching layer.
In case no snippets were found, we'll try to bust the cache by appending a random query parameter
and re-run `Plausible.Verification.Checks.FetchBody` and `Plausible.Verification.Checks.Snippet` checks.
In case no snippets were found, we'll try to bust the cache by appending
a random query parameter and re-run `FetchBody` and `Snippet` checks.
If the result is different this time, we'll assume cache likely.
"""
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
alias Plausible.InstallationSupport.{LegacyVerification, Checks, URL}
@impl true
def report_progress_as, do: "We're looking for the Plausible snippet on your site"
@ -15,7 +18,7 @@ defmodule Plausible.Verification.Checks.SnippetCacheBust do
def perform(
%State{
url: url,
diagnostics: %Diagnostics{
diagnostics: %LegacyVerification.Diagnostics{
snippets_found_in_head: 0,
snippets_found_in_body: 0,
body_fetched?: true
@ -23,10 +26,10 @@ defmodule Plausible.Verification.Checks.SnippetCacheBust do
} = state
) do
state2 =
%{state | url: Plausible.Verification.URL.bust_url(url)}
|> Plausible.Verification.Checks.FetchBody.perform()
|> Plausible.Verification.Checks.ScanBody.perform()
|> Plausible.Verification.Checks.Snippet.perform()
%{state | url: URL.bust_url(url)}
|> Checks.FetchBody.perform()
|> Checks.ScanBody.perform()
|> Checks.Snippet.perform()
if state2.diagnostics.snippets_found_in_head > 0 or
state2.diagnostics.snippets_found_in_body > 0 do

View File

@ -0,0 +1,32 @@
defmodule Plausible.InstallationSupport do
@moduledoc """
This top level module is the middle ground between pre-installation
site scans and verification of whether Plausible has been installed
correctly.
Defines the user-agent used by Elixir-native HTTP requests as well
as headless browser checks on the client side via Browserless.
"""
use Plausible
on_ee do
def user_agent() do
"Plausible Verification Agent - if abused, contact support@plausible.io"
end
def browserless_function_api_endpoint() do
config = Application.fetch_env!(:plausible, __MODULE__)
token = Keyword.fetch!(config, :token)
endpoint = Keyword.fetch!(config, :endpoint)
Path.join(endpoint, "function?token=#{token}&stealth")
end
else
def browserless_function_api_endpoint() do
"Browserless API should not be called on Community Edition"
end
def user_agent() do
"Plausible Community Edition"
end
end
end

View File

@ -0,0 +1,48 @@
defmodule Plausible.InstallationSupport.LegacyVerification.Checks do
@moduledoc """
Checks that are performed during v1 site verification.
In async execution, each check notifies the caller by sending a message to it.
"""
alias Plausible.InstallationSupport.LegacyVerification
alias Plausible.InstallationSupport.{State, CheckRunner, Checks}
require Logger
@checks [
Checks.FetchBody,
Checks.CSP,
Checks.ScanBody,
Checks.Snippet,
Checks.SnippetCacheBust,
Checks.Installation
]
def run(url, data_domain, opts \\ []) do
checks = Keyword.get(opts, :checks, @checks)
report_to = Keyword.get(opts, :report_to, self())
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
init_state =
%State{
url: url,
data_domain: data_domain,
report_to: report_to,
diagnostics: %LegacyVerification.Diagnostics{}
}
CheckRunner.run(init_state, checks,
async?: async?,
report_to: report_to,
slowdown: slowdown
)
end
def interpret_diagnostics(%State{} = state) do
LegacyVerification.Diagnostics.interpret(
state.diagnostics,
state.url
)
end
end

View File

@ -1,10 +1,10 @@
defmodule Plausible.Verification.Diagnostics do
defmodule Plausible.InstallationSupport.LegacyVerification.Diagnostics do
@moduledoc """
Module responsible for translating diagnostics to user-friendly errors and recommendations.
"""
require Logger
@errors Plausible.Verification.Errors.all()
@errors Plausible.InstallationSupport.LegacyVerification.Errors.all()
defstruct plausible_installed?: false,
snippets_found_in_head: 0,

View File

@ -1,6 +1,6 @@
defmodule Plausible.Verification.Errors do
defmodule Plausible.InstallationSupport.LegacyVerification.Errors do
@moduledoc """
A go-to definition of all verification errors
A go-to definition of all legacy verification errors
"""
@errors %{

View File

@ -1,17 +1,26 @@
defmodule Plausible.Verification.State do
defmodule Plausible.InstallationSupport.State do
@moduledoc """
The struct and interface describing the state of the site verification process.
Assigns are meant to be used to communicate between checks, while diagnostics
are later on interpreted (translated into user-friendly messages and recommendations)
via `Plausible.Verification.Diagnostics` module.
The state to be shared across check during site installation support.
Assigns are meant to be used to communicate between checks, while
`diagnostics` are specific to the check group being executed.
"""
defstruct url: nil,
data_domain: nil,
report_to: nil,
assigns: %{},
diagnostics: %Plausible.Verification.Diagnostics{}
diagnostics: %{}
@type t() :: %__MODULE__{}
@type diagnostics_type :: Plausible.InstallationSupport.LegacyVerification.Diagnostics.t()
@type t :: %__MODULE__{
url: String.t() | nil,
data_domain: String.t() | nil,
report_to: pid() | nil,
assigns: map(),
diagnostics: diagnostics_type()
}
def assign(%__MODULE__{} = state, assigns) do
%{state | assigns: Map.merge(state.assigns, Enum.into(assigns, %{}))}

View File

@ -1,6 +1,6 @@
defmodule Plausible.Verification.URL do
defmodule Plausible.InstallationSupport.URL do
@moduledoc """
Busting some caches by appending ?plausible_verification=12345 to it.
URL utilities for installation support, including cache busting functionality.
"""
def bust_url(url) do

View File

@ -124,11 +124,13 @@ defmodule Plausible.PromEx.Plugins.PlausibleMetrics do
),
counter(
metric_prefix ++ [:verification, :js_elixir_diff],
event_name: Plausible.Verification.Checks.Installation.telemetry_event(_diff = true)
event_name:
Plausible.InstallationSupport.Checks.Installation.telemetry_event(_diff = true)
),
counter(
metric_prefix ++ [:verification, :js_elixir_match],
event_name: Plausible.Verification.Checks.Installation.telemetry_event(_diff = false)
event_name:
Plausible.InstallationSupport.Checks.Installation.telemetry_event(_diff = false)
)
]
)

View File

@ -1,16 +0,0 @@
defmodule Plausible.Verification do
@moduledoc """
Module defining the user-agent used for site verification.
"""
use Plausible
on_ee do
def user_agent() do
"Plausible Verification Agent - if abused, contact support@plausible.io"
end
else
def user_agent() do
"Plausible Community Edition"
end
end
end

View File

@ -1,37 +0,0 @@
defmodule Plausible.Verification.Check do
@moduledoc """
Behaviour to be implemented by specific site verification checks.
`report_progress_as()` doesn't necessarily reflect the actual check description,
it serves as a user-facing message grouping mechanism, to prevent frequent message flashing when checks rotate often.
Each check operates on `state()` and is expected to return it, optionally modified, by all means.
`perform_safe/1` is used to guarantee no exceptions are thrown by faulty implementations, not to interrupt LiveView.
"""
@type state() :: Plausible.Verification.State.t()
@callback report_progress_as() :: String.t()
@callback perform(state()) :: state()
defmacro __using__(_) do
quote do
import Plausible.Verification.State
alias Plausible.Verification.Checks
alias Plausible.Verification.State
alias Plausible.Verification.Diagnostics
require Logger
@behaviour Plausible.Verification.Check
def perform_safe(state) do
perform(state)
catch
_, e ->
Logger.error(
"Error running check #{inspect(__MODULE__)} on #{state.url}: #{inspect(e)}"
)
put_diagnostics(state, service_error: e)
end
end
end
end

View File

@ -1,75 +0,0 @@
defmodule Plausible.Verification.Checks do
@moduledoc """
Checks that are performed during site verification.
Each module defined in `@checks` implements the `Plausible.Verification.Check` behaviour.
Checks are normally run asynchronously, except when synchronous execution is optionally required
for tests. Slowdowns can be optionally added, the user doesn't benefit from running the checks too quickly.
In async execution, each check notifies the caller by sending a message to it.
"""
alias Plausible.Verification.Checks
alias Plausible.Verification.State
require Logger
@checks [
Checks.FetchBody,
Checks.CSP,
Checks.ScanBody,
Checks.Snippet,
Checks.SnippetCacheBust,
Checks.Installation
]
def run(url, data_domain, opts \\ []) do
checks = Keyword.get(opts, :checks, @checks)
report_to = Keyword.get(opts, :report_to, self())
async? = Keyword.get(opts, :async?, true)
slowdown = Keyword.get(opts, :slowdown, 500)
if async? do
Task.start_link(fn -> do_run(url, data_domain, checks, report_to, slowdown) end)
else
do_run(url, data_domain, checks, report_to, slowdown)
end
end
def interpret_diagnostics(%State{} = state) do
Plausible.Verification.Diagnostics.interpret(state.diagnostics, state.url)
end
defp do_run(url, data_domain, checks, report_to, slowdown) do
init_state = %State{url: url, data_domain: data_domain, report_to: report_to}
state =
Enum.reduce(
checks,
init_state,
fn check, state ->
state
|> notify_start(check, slowdown)
|> check.perform_safe()
end
)
notify_verification_end(state, slowdown)
end
defp notify_start(state, check, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:verification_check_start, {check, state}})
end
state
end
defp notify_verification_end(state, slowdown) do
if is_pid(state.report_to) do
if is_integer(slowdown) and slowdown > 0, do: :timer.sleep(slowdown)
send(state.report_to, {:verification_end, state})
end
state
end
end

View File

@ -7,6 +7,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
use Plausible
alias PlausibleWeb.Router.Helpers, as: Routes
alias Plausible.InstallationSupport.{State, LegacyVerification}
import PlausibleWeb.Components.Generic
@ -19,8 +20,8 @@ defmodule PlausibleWeb.Live.Components.Verification do
attr(:super_admin?, :boolean, default: false)
attr(:finished?, :boolean, default: false)
attr(:success?, :boolean, default: false)
attr(:verification_state, Plausible.Verification.State, default: nil)
attr(:interpretation, Plausible.Verification.Diagnostics.Result, default: nil)
attr(:verification_state, State, default: nil)
attr(:interpretation, LegacyVerification.Diagnostics.Result, default: nil)
attr(:attempts, :integer, default: 0)
attr(:flow, :string, default: "")
attr(:installation_type, :string, default: nil)

View File

@ -3,7 +3,7 @@ defmodule PlausibleWeb.Live.Installation do
User assistance module around Plausible installation instructions/onboarding
"""
use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State}
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@script_extension_params %{
"outbound_links" => "outbound-links",
@ -60,7 +60,7 @@ defmodule PlausibleWeb.Live.Installation do
end)
if connected?(socket) and is_nil(installation_type) do
Checks.run("https://#{domain}", domain,
LegacyVerification.Checks.run("https://#{domain}", domain,
checks: [
Checks.FetchBody,
Checks.ScanBody
@ -86,7 +86,7 @@ defmodule PlausibleWeb.Live.Installation do
end
end
def handle_info({:verification_end, %State{} = state}, socket) do
def handle_info({:all_checks_done, %State{} = state}, socket) do
installation_type =
case state.diagnostics do
%{wordpress_likely?: true} -> "wordpress"

View File

@ -3,8 +3,8 @@ defmodule PlausibleWeb.Live.InstallationV2 do
User assistance module around Plausible installation instructions/onboarding
"""
alias PlausibleWeb.Flows
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State}
def mount(
%{"domain" => domain} = params,
@ -34,7 +34,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
connected?(socket) and flow == Flows.provisioning() and !params["type"]
if detect_installation_type? do
Checks.run("https://#{site.domain}", site.domain,
LegacyVerification.Checks.run("https://#{site.domain}", site.domain,
checks: [
Checks.FetchBody,
Checks.ScanBody
@ -62,7 +62,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
)}
end
def handle_info({:verification_end, %State{} = state}, socket) do
def handle_info({:all_checks_done, %State{} = state}, socket) do
installation_type =
case state.diagnostics do
%{wordpress_likely?: true} -> "wordpress"
@ -77,7 +77,7 @@ defmodule PlausibleWeb.Live.InstallationV2 do
)}
end
def handle_info({:verification_check_start, _}, socket) do
def handle_info({:check_start, _}, socket) do
{:noreply, socket}
end

View File

@ -7,7 +7,7 @@ defmodule PlausibleWeb.Live.Verification do
use Plausible
use PlausibleWeb, :live_view
alias Plausible.Verification.{Checks, State}
alias Plausible.InstallationSupport.{State, LegacyVerification}
@component PlausibleWeb.Live.Components.Verification
@slowdown_for_frequent_checking :timer.seconds(5)
@ -132,7 +132,7 @@ defmodule PlausibleWeb.Live.Verification do
end
{:ok, pid} =
Checks.run(
LegacyVerification.Checks.run(
"https://#{socket.assigns.domain}",
socket.assigns.domain,
report_to: report_to,
@ -143,7 +143,7 @@ defmodule PlausibleWeb.Live.Verification do
end
end
def handle_info({:verification_check_start, {check, _state}}, socket) do
def handle_info({:check_start, {check, _state}}, socket) do
update_component(socket,
message: check.report_progress_as()
)
@ -151,8 +151,8 @@ defmodule PlausibleWeb.Live.Verification do
{:noreply, socket}
end
def handle_info({:verification_end, %State{} = state}, socket) do
interpretation = Checks.interpret_diagnostics(state)
def handle_info({:all_checks_done, %State{} = state}, socket) do
interpretation = LegacyVerification.Checks.interpret_diagnostics(state)
if not socket.assigns.has_pageviews? do
schedule_pageviews_check(socket)

View File

@ -80,7 +80,7 @@ defmodule Plausible.Ingestion.EventTest do
assert_receive :telemetry_handled
end
test "drops verification agent" do
test "drops installation support user agent" do
site = new_site()
payload = %{
@ -90,7 +90,7 @@ defmodule Plausible.Ingestion.EventTest do
conn =
build_conn(:post, "/api/events", payload)
|> Plug.Conn.put_req_header("user-agent", Plausible.Verification.user_agent())
|> Plug.Conn.put_req_header("user-agent", Plausible.InstallationSupport.user_agent())
assert {:ok, request} = Request.build(conn)

View File

@ -1,17 +1,18 @@
defmodule Plausible.Verification.Checks.CSPTest do
defmodule Plausible.InstallationSupport.Checks.CSPTest do
use Plausible.DataCase, async: true
alias Plausible.Verification.State
alias Plausible.InstallationSupport.{State, LegacyVerification}
@check Plausible.Verification.Checks.CSP
@check Plausible.InstallationSupport.Checks.CSP
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips no headers" do
state = %State{}
state = @default_state
assert ^state = @check.perform(state)
end
test "skips no headers 2" do
state = %State{} |> State.assign(headers: %{})
state = @default_state |> State.assign(headers: %{})
assert ^state = @check.perform(state)
end
@ -19,7 +20,7 @@ defmodule Plausible.Verification.Checks.CSPTest do
headers = %{"content-security-policy" => ["default-src 'self' foo.local; example.com"]}
state =
%State{}
@default_state
|> State.assign(headers: headers)
|> @check.perform()
@ -30,7 +31,7 @@ defmodule Plausible.Verification.Checks.CSPTest do
headers = %{"content-security-policy" => ["default-src 'self' example.com; localhost"]}
state =
%State{}
@default_state
|> State.assign(headers: headers)
|> @check.perform()

View File

@ -1,9 +1,11 @@
defmodule Plausible.Verification.Checks.FetchBodyTest do
defmodule Plausible.InstallationSupport.Checks.FetchBodyTest do
use Plausible.DataCase, async: true
import Plug.Conn
@check Plausible.Verification.Checks.FetchBody
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Checks.FetchBody
@normal_body """
<html>
@ -16,8 +18,9 @@ defmodule Plausible.Verification.Checks.FetchBodyTest do
setup do
{:ok,
state: %Plausible.Verification.State{
url: "https://example.com"
state: %State{
url: "https://example.com",
diagnostics: %LegacyVerification.Diagnostics{}
}}
end

View File

@ -1,18 +1,18 @@
defmodule Plausible.Verification.Checks.ScanBodyTest do
defmodule Plausible.InstallationSupport.Checks.ScanBodyTest do
use Plausible.DataCase, async: true
alias Plausible.Verification.State
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Plausible.Verification.Checks.ScanBody
@check Checks.ScanBody
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips on no raw body" do
state = %State{}
assert ^state = @check.perform(state)
assert @default_state = @check.perform(@default_state)
end
test "detects nothing" do
state =
%State{}
@default_state
|> State.assign(raw_body: "...")
|> @check.perform()
@ -22,7 +22,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM" do
state =
%State{}
@default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js...")
|> @check.perform()
@ -33,7 +33,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM and cookie banner" do
state =
%State{}
@default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js...cookiebot...")
|> @check.perform()
@ -45,7 +45,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
for signature <- ["wp-content", "wp-includes", "wp-json"] do
test "detects WordPress: #{signature}" do
state =
%State{}
@default_state
|> State.assign(raw_body: "...#{unquote(signature)}...")
|> @check.perform()
@ -57,7 +57,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects GTM and WordPress" do
state =
%State{}
@default_state
|> State.assign(raw_body: "...googletagmanager.com/gtm.js....wp-content...")
|> @check.perform()
@ -72,7 +72,7 @@ defmodule Plausible.Verification.Checks.ScanBodyTest do
test "detects official plugin" do
state =
%State{}
@default_state
|> State.assign(raw_body: @d, document: Floki.parse_document!(@d))
|> @check.perform()

View File

@ -1,12 +1,13 @@
defmodule Plausible.Verification.Checks.SnippetTest do
defmodule Plausible.InstallationSupport.Checks.SnippetTest do
use Plausible.DataCase, async: true
alias Plausible.Verification.State
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
@check Plausible.Verification.Checks.Snippet
@check Checks.Snippet
@default_state %State{diagnostics: %LegacyVerification.Diagnostics{}}
test "skips when there's no document" do
state = %State{}
state = @default_state
assert ^state = @check.perform(state)
end
@ -156,7 +157,7 @@ defmodule Plausible.Verification.Checks.SnippetTest do
[data_domain: "example.com"]
|> Keyword.merge(opts)
State
@default_state
|> struct!(opts)
|> State.assign(document: doc)
end

View File

@ -1,14 +1,12 @@
defmodule Plausible.Verification.ChecksTest do
defmodule Plausible.InstallationSupport.LegacyVerification.ChecksTest do
use Plausible.DataCase, async: true
alias Plausible.Verification.Checks
alias Plausible.Verification.Diagnostics
alias Plausible.Verification.State
alias Plausible.InstallationSupport.{State, Checks, LegacyVerification}
import ExUnit.CaptureLog
import Plug.Conn
@errors Plausible.Verification.Errors.all()
@errors LegacyVerification.Errors.all()
describe "successful verification" do
@normal_body """
@ -25,7 +23,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end
@ -33,7 +31,7 @@ defmodule Plausible.Verification.ChecksTest do
ref = :counters.new(1, [:atomics])
test = self()
Req.Test.stub(Plausible.Verification.Checks.FetchBody, fn conn ->
Req.Test.stub(Checks.FetchBody, fn conn ->
if :counters.get(ref, 1) < 2 do
:counters.add(ref, 1, 1)
send(test, :redirect_sent)
@ -51,7 +49,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
assert_receive :redirect_sent
@ -76,7 +74,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end
@ -94,7 +92,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end
@ -107,7 +105,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 202))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end
@ -124,7 +122,7 @@ defmodule Plausible.Verification.ChecksTest do
ref = :counters.new(1, [:atomics])
test = self()
Req.Test.stub(Plausible.Verification.Checks.FetchBody, fn conn ->
Req.Test.stub(Checks.FetchBody, fn conn ->
if :counters.get(ref, 1) == 0 do
:counters.add(ref, 1, 1)
send(test, :redirect_sent)
@ -142,7 +140,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
assert_receive :redirect_sent
@ -155,7 +153,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(400, %{})
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.temporary)
end
@ -167,7 +165,7 @@ defmodule Plausible.Verification.ChecksTest do
{_, log} =
with_log(fn ->
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end)
@ -190,7 +188,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com")
assert_receive :redirect_sent
@ -217,7 +215,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.snippet_in_body)
end
@ -242,7 +240,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.multiple_snippets)
end
@ -262,7 +260,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet)
end
@ -283,7 +281,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_ok()
end
@ -315,7 +313,7 @@ defmodule Plausible.Verification.ChecksTest do
end)
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_general)
end
@ -357,7 +355,7 @@ defmodule Plausible.Verification.ChecksTest do
end)
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_wp_no_plugin)
end
@ -400,7 +398,7 @@ defmodule Plausible.Verification.ChecksTest do
end)
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.cache_wp_plugin)
end
@ -409,7 +407,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet)
end
@ -429,13 +427,13 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet_wp)
end
test "a check that raises" do
defmodule FaultyCheckRaise do
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "Faulty check"
@ -450,16 +448,16 @@ defmodule Plausible.Verification.ChecksTest do
end)
assert log =~
~s|Error running check Plausible.Verification.ChecksTest.FaultyCheckRaise on https://example.com: %RuntimeError{message: "boom"}|
~s|Error running check Plausible.InstallationSupport.LegacyVerification.ChecksTest.FaultyCheckRaise on https://example.com: %RuntimeError{message: "boom"}|
result
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com")
end
test "a check that throws" do
defmodule FaultyCheckThrow do
use Plausible.Verification.Check
use Plausible.InstallationSupport.Check
@impl true
def report_progress_as, do: "Faulty check"
@ -474,10 +472,10 @@ defmodule Plausible.Verification.ChecksTest do
end)
assert log =~
~s|Error running check Plausible.Verification.ChecksTest.FaultyCheckThrow on https://example.com: :boom|
~s|Error running check Plausible.InstallationSupport.LegacyVerification.ChecksTest.FaultyCheckThrow on https://example.com: :boom|
result
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com")
end
@ -492,7 +490,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.csp)
end
@ -507,7 +505,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.no_snippet)
end
@ -517,13 +515,15 @@ defmodule Plausible.Verification.ChecksTest do
final_state = run_checks(report_to: self())
assert_receive {:verification_check_start, {Checks.FetchBody, %State{}}}
assert_receive {:verification_check_start, {Checks.CSP, %State{}}}
assert_receive {:verification_check_start, {Checks.ScanBody, %State{}}}
assert_receive {:verification_check_start, {Checks.Snippet, %State{}}}
assert_receive {:verification_check_start, {Checks.SnippetCacheBust, %State{}}}
assert_receive {:verification_check_start, {Checks.Installation, %State{}}}
assert_receive {:verification_end, %State{} = ^final_state}
assert_receive {:check_start, {Checks.FetchBody, %State{}}}
assert_receive {:check_start, {Checks.CSP, %State{}}}
assert_receive {:check_start, {Checks.ScanBody, %State{}}}
assert_receive {:check_start, {Checks.Snippet, %State{}}}
assert_receive {:check_start, {Checks.SnippetCacheBust, %State{}}}
assert_receive {:check_start, {Checks.Installation, %State{}}}
assert_receive {:all_checks_done, %State{} = ^final_state}
refute_receive _
end
@ -555,7 +555,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.csp)
end
@ -564,7 +564,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.gtm)
end
@ -591,7 +591,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.gtm_cookie_banner)
end
@ -605,7 +605,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unreachable, url: "https://example.com")
end
@ -614,7 +614,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_misconfigured)
end
@ -633,7 +633,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_wp_no_plugin)
end
@ -642,13 +642,13 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0))
result = run_checks()
interpretation = Checks.interpret_diagnostics(result)
interpretation = LegacyVerification.Checks.interpret_diagnostics(result)
refute interpretation.ok?
assert interpretation.errors == ["We encountered an error with your Plausible proxy"]
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.proxy_general)
end
@ -657,7 +657,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unknown)
end
@ -675,7 +675,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_general)
end
@ -694,7 +694,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_wp_no_plugin)
end
@ -714,7 +714,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(false, 0))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.illegal_attrs_wp_plugin)
end
@ -723,7 +723,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.generic)
end
@ -732,7 +732,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.old_script_wp_no_plugin)
end
@ -741,7 +741,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, -1))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.old_script_wp_plugin)
end
@ -750,7 +750,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation(200, plausible_installed(true, 500))
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.unknown)
end
@ -759,7 +759,7 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.different_data_domain, domain: "example.com")
end
@ -780,14 +780,14 @@ defmodule Plausible.Verification.ChecksTest do
stub_installation()
run_checks()
|> Checks.interpret_diagnostics()
|> LegacyVerification.Checks.interpret_diagnostics()
|> assert_error(@errors.different_data_domain, domain: "example.com")
end
end
describe "unhhandled cases from sentry" do
test "APP-58: 4b1435e3f8a048eb949cc78fa578d1e4" do
%Plausible.Verification.Diagnostics{
%LegacyVerification.Diagnostics{
plausible_installed?: true,
snippets_found_in_head: 0,
snippets_found_in_body: 0,
@ -810,7 +810,7 @@ defmodule Plausible.Verification.ChecksTest do
end
test "service timeout" do
%Plausible.Verification.Diagnostics{
%LegacyVerification.Diagnostics{
plausible_installed?: false,
snippets_found_in_head: 1,
snippets_found_in_body: 0,
@ -833,7 +833,7 @@ defmodule Plausible.Verification.ChecksTest do
end
test "malformed snippet code, that headless somewhat accepts" do
%Plausible.Verification.Diagnostics{
%LegacyVerification.Diagnostics{
plausible_installed?: true,
snippets_found_in_head: 0,
snippets_found_in_body: 0,
@ -856,7 +856,7 @@ defmodule Plausible.Verification.ChecksTest do
end
test "gtm+wp detected, but likely script id attribute interfering" do
%Plausible.Verification.Diagnostics{
%LegacyVerification.Diagnostics{
plausible_installed?: false,
snippets_found_in_head: 1,
snippets_found_in_body: 0,
@ -881,12 +881,12 @@ defmodule Plausible.Verification.ChecksTest do
defp interpret_sentry_case(diagnostics) do
diagnostics
|> Diagnostics.interpret("example.com")
|> LegacyVerification.Diagnostics.interpret("example.com")
|> refute_unhandled()
end
defp run_checks(extra_opts \\ []) do
Checks.run(
LegacyVerification.Checks.run(
"https://example.com",
"example.com",
Keyword.merge([async?: false, report_to: nil, slowdown: 0], extra_opts)
@ -894,11 +894,11 @@ defmodule Plausible.Verification.ChecksTest do
end
defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f)
Req.Test.stub(Checks.FetchBody, f)
end
defp stub_installation(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.Installation, f)
Req.Test.stub(Checks.Installation, f)
end
defp stub_fetch_body(status, body) do

View File

@ -3,6 +3,8 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
import Phoenix.LiveViewTest, only: [render_component: 2]
import Plausible.Test.Support.HTML
alias Plausible.InstallationSupport.{State, LegacyVerification}
@component PlausibleWeb.Live.Components.Verification
@progress ~s|#progress-indicator p#progress|
@ -36,8 +38,9 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
test "renders diagnostic interpretation" do
interpretation =
Plausible.Verification.Checks.interpret_diagnostics(%Plausible.Verification.State{
url: "example.com"
LegacyVerification.Checks.interpret_diagnostics(%State{
url: "example.com",
diagnostics: %LegacyVerification.Diagnostics{}
})
html =
@ -58,12 +61,13 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do
end
test "renders super-admin report" do
state = %Plausible.Verification.State{
url: "example.com"
state = %State{
url: "example.com",
diagnostics: %LegacyVerification.Diagnostics{}
}
interpretation =
Plausible.Verification.Checks.interpret_diagnostics(state)
LegacyVerification.Checks.interpret_diagnostics(state)
html =
render_component(@component,

View File

@ -355,7 +355,7 @@ defmodule PlausibleWeb.Live.InstallationTest do
end
defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f)
Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end
defp stub_fetch_body(status, body) do

View File

@ -279,7 +279,7 @@ defmodule PlausibleWeb.Live.InstallationV2Test do
end
defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f)
Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end
defp stub_fetch_body(status, body) do

View File

@ -184,11 +184,11 @@ defmodule PlausibleWeb.Live.VerificationTest do
end
defp stub_fetch_body(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.FetchBody, f)
Req.Test.stub(Plausible.InstallationSupport.Checks.FetchBody, f)
end
defp stub_installation(f) when is_function(f, 1) do
Req.Test.stub(Plausible.Verification.Checks.Installation, f)
Req.Test.stub(Plausible.InstallationSupport.Checks.Installation, f)
end
defp stub_fetch_body(status, body) do

View File

@ -3,6 +3,16 @@
This document describes the design goals informing the architecture of the Plausible tracker script codebase as well as a map
to how the code is laid out.
## Installation Support
The tracker subdirectory also includes site verification and pre-installation checks that are run in headless browser, via
browserless.io. These files live under the `/tracker/installation-support/` director and are meant to provide Plausible
installation support - checking the site for what technologies to recommend and verifying whether Plausible has been
installed correctly. Please see `lib/plausible/installation_support/checks/installation.ex` for the Elixir context and how
this JS code ends up being used.
While this logic could be separated from the tracker script, it's convenient for installation support to reuse the Playwright test structure and JS compilation logic without introducing yet another subdirectory with its own dependencies.
## Design goals
We want to provide users a javascript suite to capture page views and activities done on the web page.

View File

@ -31,10 +31,16 @@
},
"npm_package": "esm"
},
{
"name": "detector.js",
"entry_point": "installation_support/detector.js",
"output_path": "priv/tracker/installation_support/detector.js",
"globals": {}
},
{
"name": "verifier-v1.js",
"entry_point": "verifier/verifier-v1.js",
"output_path": "priv/tracker/verifier/verifier-v1.js",
"entry_point": "installation_support/verifier-v1.js",
"output_path": "priv/tracker/installation_support/verifier-v1.js",
"globals": {}
}
],

View File

@ -0,0 +1,35 @@
import { waitForSnippetsV1 } from "./snippet-checks"
import { checkWordPress } from "./check-wordpress"
import { checkGTM } from "./check-gtm"
window.scanPageBeforePlausibleInstallation = async function(detectV1, debug) {
function log(message) {
if (debug) console.log('[Plausible Verification]', message)
}
const {wordpressPlugin, wordpressLikely} = checkWordPress(document)
log(`wordpressPlugin: ${wordpressPlugin}`)
log(`wordpressLikely: ${wordpressLikely}`)
const gtmLikely = checkGTM(document)
log(`gtmLikely: ${gtmLikely}`)
// Cannot implement yet: we should detect the WP plugin version here and
// decide `v1Detected` based on that. For now we assume WP plugin is v1.
let v1Detected = wordpressPlugin
if (!v1Detected && detectV1) {
const snippetData = await waitForSnippetsV1(log)
v1Detected = snippetData.counts.all > 0
}
return {
data: {
completed: true,
v1Detected: v1Detected,
wordpressPlugin: wordpressPlugin,
wordpressLikely: wordpressLikely,
gtmLikely: gtmLikely,
}
}
}

View File

@ -15,7 +15,11 @@ export default defineConfig({
retries: process.env.CI ? 1 : 0,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'list',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
/*
Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions.
NOTE: We run the installation support tests on Chrome only because the Browserless /function API
runs a Chromium-based browser environment.
*/
projects: [
{
name: 'chromium',
@ -25,13 +29,13 @@ export default defineConfig({
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
testIgnore: 'test/verifier/**',
testIgnore: 'test/installation_support/**',
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
testIgnore: 'test/verifier/**',
testIgnore: 'test/installation_support/**',
},
],
webServer: {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkDataDomainMismatch } from '../../verifier/check-data-domain-mismatch'
import { checkDataDomainMismatch } from '../../installation_support/check-data-domain-mismatch'
function mockSnippet(dataDomain) {
return { getAttribute: _ => dataDomain }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkGTM } from '../../verifier/check-gtm'
import { checkGTM } from '../../installation_support/check-gtm'
function mockDocument(html) {
return {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkManualExtension } from '../../verifier/check-manual-extension'
import { checkManualExtension } from '../../installation_support/check-manual-extension'
function mockSnippet(dataDomain) {
return { getAttribute: _ => dataDomain }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkProxyLikely } from '../../verifier/check-proxy-likely'
import { checkProxyLikely } from '../../installation_support/check-proxy-likely'
function mockSnippet(src) {
return { getAttribute: _ => src }

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkUnknownAttributes } from '../../verifier/check-unknown-attributes.js'
import { checkUnknownAttributes } from '../../installation_support/check-unknown-attributes.js'
test.describe('checkUnknownAttributes', () => {
test('returns false when no snippets', () => {

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import { checkWordPress, WORDPRESS_PLUGIN_VERSION_SELECTOR } from '../../verifier/check-wordpress'
import { checkWordPress, WORDPRESS_PLUGIN_VERSION_SELECTOR } from '../../installation_support/check-wordpress'
function mockDocument(html, hasMetaTag) {
return {

View File

@ -0,0 +1,56 @@
import { test, expect } from '@playwright/test'
import { detect } from '../support/installation-support-playwright-wrappers'
import { initializePageDynamically } from '../support/initialize-page-dynamically'
test.describe('detector.js (basic diagnostics)', () => {
test('skips v1 snippet detection by default', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: `
<html>
<head>
<script src="" data-domain=""></script>
</head>
</html>
`
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.v1Detected).toBe(false)
})
test('detects WP plugin, WP and GTM', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: `
<html>
<head>
<link rel="icon" href="https://example.com/wp-content/uploads/favicon.ico" sizes="32x32">
<meta name="plausible-analytics-version" content="2.3.1">
<script async src="https://www.googletagmanager.com/gtm.js?id=GTM-123"></script>
</head>
</html>
`
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.wordpressPlugin).toBe(true)
expect(result.data.wordpressLikely).toBe(true)
expect(result.data.gtmLikely).toBe(true)
})
test('No WP plugin, WP or GTM', async ({ page }, { testId }) => {
const { url } = await initializePageDynamically(page, {
testId,
response: '<html><head></head></html>'
})
const result = await detect(page, {url: url, detectV1: false})
expect(result.data.wordpressPlugin).toBe(false)
expect(result.data.wordpressLikely).toBe(false)
expect(result.data.gtmLikely).toBe(false)
})
})

View File

@ -1,5 +1,5 @@
import { test, expect } from '@playwright/test'
import verify from '../support/verify-playwright-wrapper'
import { verify } from '../support/installation-support-playwright-wrappers'
import { delay } from '../support/test-utils'
import { initializePageDynamically } from '../support/initialize-page-dynamically'
import { compileFile } from '../../compiler'

View File

@ -0,0 +1,33 @@
import { compileFile } from '../../compiler/index.js'
import variantsFile from '../../compiler/variants.json' with { type: 'json' }
const VERIFIER_V1_JS_VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'verifier-v1.js')
const DETECTOR_JS_VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'detector.js')
export async function verify(page, context) {
const {url, expectedDataDomain} = context
const debug = context.debug ? true : false
const verifierCode = await compileFile(VERIFIER_V1_JS_VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(verifierCode)
return await page.evaluate(async ({expectedDataDomain, debug}) => {
return await window.verifyPlausibleInstallation(expectedDataDomain, debug)
}, {expectedDataDomain, debug})
}
export async function detect(page, context) {
const {url, detectV1} = context
const debug = context.debug ? true : false
const detectorCode = await compileFile(DETECTOR_JS_VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(detectorCode)
return await page.evaluate(async ({detectV1, debug}) => {
return await window.scanPageBeforePlausibleInstallation(detectV1, debug)
}, {detectV1, debug})
}

View File

@ -1,18 +0,0 @@
import { compileFile } from '../../compiler/index.js'
import variantsFile from '../../compiler/variants.json' with { type: 'json' }
const VARIANT = variantsFile.manualVariants.find(variant => variant.name === 'verifier-v1.js')
export default async function verify(page, context) {
const {url, expectedDataDomain} = context
const debug = context.debug ? true : false
const verifierCode = await compileFile(VARIANT, { returnCode: true })
await page.goto(url)
await page.evaluate(verifierCode)
return await page.evaluate(async ({expectedDataDomain, debug}) => {
return await window.verifyPlausibleInstallation(expectedDataDomain, debug)
}, {expectedDataDomain, debug})
}