Google APIs integration improvements (#2358)

* Make TestUtils module available in all tests

* Add macros patching the application env in tests

Unfortunately a lot of existing functionality relies on
certain application env setup. This isn't ideal because
the app config is a shared state that prevents us from
running the tests in parallel.

Those macros encapsulate setting up new env for test purposes
and make sure the changes are reverted when the test finishes.

* Allow passing request opts to HTTPClient.post/4

We need this to swap custom request building in
Google Analytics import.

* Unify errors when listing sites

* React: propagate backend error messages if available

* React: catch API errors in Search Terms component

* Propagate google API errors on referrer drilldown

* Handle verified properties errors in SC settings

* Add missing tests for SC settings controller

* Unify errors for fetching search analytics queries (list stats)

* Unify errors refreshing Google Auth Token

* Test fetch_stats/3 errors and replace Double with Mox

* Fixup makrup

* s/class/className

* Simplify Search Terms display in case of errors

* Fix warnings
This commit is contained in:
Adam Rutkowski 2022-10-24 09:34:02 +02:00 committed by GitHub
parent 93bb62ef27
commit 0fa6b688af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 427 additions and 191 deletions

View File

@ -4,9 +4,10 @@ let abortController = new AbortController()
let SHARED_LINK_AUTH = null let SHARED_LINK_AUTH = null
class ApiError extends Error { class ApiError extends Error {
constructor(message) { constructor(message, payload) {
super(message); super(message)
this.name = "ApiError"; this.name = "ApiError"
this.payload = payload
} }
} }
@ -56,7 +57,7 @@ export function get(url, query={}, ...extraQuery) {
.then( response => { .then( response => {
if (!response.ok) { if (!response.ok) {
return response.json().then((msg) => { return response.json().then((msg) => {
throw new ApiError(msg.error) throw new ApiError(msg.error, msg)
}) })
} }
return response.json() return response.json()

View File

@ -30,7 +30,11 @@ export default class SearchTerms extends React.Component {
searchTerms: res.search_terms || [], searchTerms: res.search_terms || [],
notConfigured: res.not_configured, notConfigured: res.not_configured,
isAdmin: res.is_admin isAdmin: res.is_admin
})) })).catch((error) =>
{
this.setState({ loading: false, searchTerms: [], notConfigured: true, error: true, isAdmin: error.payload.is_admin })
}
)
} }
renderSearchTerm(term) { renderSearchTerm(term) {
@ -67,8 +71,10 @@ export default class SearchTerms extends React.Component {
return ( return (
<div className="text-center text-gray-700 dark:text-gray-300 text-sm mt-20"> <div className="text-center text-gray-700 dark:text-gray-300 text-sm mt-20">
<RocketIcon /> <RocketIcon />
<div>The site is not connected to Google Search Keywords</div> <div>
<div>Cannot show search terms</div> This site is not connected to Search Console so we cannot show the search phrases.
{this.state.isAdmin && this.state.error && <><br/><br/><p>Please click below to connect your Search Console account.</p></>}
</div>
{this.state.isAdmin && <a href={`/${encodeURIComponent(this.props.site.domain)}/settings/search-console`} className="button mt-4">Connect with Google</a> } {this.state.isAdmin && <a href={`/${encodeURIComponent(this.props.site.domain)}/settings/search-console`} className="button mt-4">Connect with Google</a> }
</div> </div>
) )

View File

@ -182,9 +182,8 @@ defmodule Plausible.Google.Api do
buffer_pid = Keyword.get(opts, :buffer) buffer_pid = Keyword.get(opts, :buffer)
attempt = Keyword.get(opts, :attempt, 1) attempt = Keyword.get(opts, :attempt, 1)
sleep_time = Keyword.get(opts, :sleep_time, 1000) sleep_time = Keyword.get(opts, :sleep_time, 1000)
http_client = Keyword.get(opts, :http_client, Finch)
case HTTP.get_report(http_client, report_request) do case HTTP.get_report(report_request) do
{:ok, {rows, next_page_token}} -> {:ok, {rows, next_page_token}} ->
records = Plausible.Imported.from_google_analytics(rows, site.id, report_request.dataset) records = Plausible.Imported.from_google_analytics(rows, site.id, report_request.dataset)
:ok = Plausible.Google.Buffer.insert_many(buffer_pid, report_request.dataset, records) :ok = Plausible.Google.Buffer.insert_many(buffer_pid, report_request.dataset, records)

View File

@ -2,82 +2,65 @@ defmodule Plausible.Google.HTTP do
require Logger require Logger
alias Plausible.HTTPClient alias Plausible.HTTPClient
@spec get_report(module(), Plausible.Google.ReportRequest.t()) :: @spec get_report(Plausible.Google.ReportRequest.t()) ::
{:ok, {[map()], String.t() | nil}} | {:error, any()} {:ok, {[map()], String.t() | nil}} | {:error, any()}
def get_report(http_client, %Plausible.Google.ReportRequest{} = report_request) do def get_report(%Plausible.Google.ReportRequest{} = report_request) do
params = params = %{
Jason.encode!(%{ reportRequests: [
reportRequests: [ %{
%{ viewId: report_request.view_id,
viewId: report_request.view_id, dateRanges: [
dateRanges: [ %{
%{ startDate: report_request.date_range.first,
startDate: report_request.date_range.first, endDate: report_request.date_range.last
endDate: report_request.date_range.last }
} ],
], dimensions: Enum.map(report_request.dimensions, &%{name: &1, histogramBuckets: []}),
dimensions: Enum.map(report_request.dimensions, &%{name: &1, histogramBuckets: []}), metrics: Enum.map(report_request.metrics, &%{expression: &1}),
metrics: Enum.map(report_request.metrics, &%{expression: &1}), hideTotals: true,
hideTotals: true, hideValueRanges: true,
hideValueRanges: true, orderBys: [%{fieldName: "ga:date", sortOrder: "DESCENDING"}],
orderBys: [%{fieldName: "ga:date", sortOrder: "DESCENDING"}], pageSize: report_request.page_size,
pageSize: report_request.page_size, pageToken: report_request.page_token
pageToken: report_request.page_token }
} ]
] }
})
response = response =
:post HTTPClient.impl().post(
|> Finch.build(
"#{reporting_api_url()}/v4/reports:batchGet", "#{reporting_api_url()}/v4/reports:batchGet",
[{"Authorization", "Bearer #{report_request.access_token}"}], [{"Authorization", "Bearer #{report_request.access_token}"}],
params params,
receive_timeout: 60_000
) )
|> http_client.request(Plausible.Finch, receive_timeout: 60_000)
with {:ok, %{status: 200, body: body}} <- response, with {:ok, %{body: body}} <- response,
{:ok, report} <- parse_report_from_response(body), {:ok, report} <- parse_report_from_response(body),
token <- Map.get(report, "nextPageToken"), token <- Map.get(report, "nextPageToken"),
{:ok, report} <- convert_to_maps(report) do {:ok, report} <- convert_to_maps(report) do
{:ok, {report, token}} {:ok, {report, token}}
else else
{:ok, %{status: _non_http_200, body: _body} = response} -> {:error, %{reason: %{status: status, body: body}}} ->
report_failed_request_to_sentry(response) Sentry.Context.set_extra_context(%{ga_response: %{body: body, status: status}})
{:error, :request_failed} {:error, :request_failed}
{:error, cause} -> {:error, _} ->
{:error, cause} {:error, :request_failed}
end end
end end
defp report_failed_request_to_sentry(%{status: status, body: body}) do defp parse_report_from_response(body) do
case Jason.decode(body) do with %{"reports" => [report | _]} <- body do
{:ok, %{} = body} ->
Sentry.Context.set_extra_context(%{ga_response: %{body: body, status: status}})
_error ->
Sentry.Context.set_extra_context(%{ga_response: %{body: body, status: status}})
end
end
defp parse_report_from_response(raw_body) do
with {:ok, map} <- Jason.decode(raw_body),
%{"reports" => [report | _]} <- map do
{:ok, report} {:ok, report}
else else
{:error, cause} -> _ ->
Logger.error("Google Analytics: Failed to parse JSON. Reason: #{inspect(cause)}") Sentry.Context.set_extra_context(%{google_analytics_response: body})
Sentry.Context.set_extra_context(%{google_analytics_response: raw_body})
{:error, cause}
%{} = response ->
Logger.error( Logger.error(
"Google Analytics: Failed to find report in response. Reason: #{inspect(response)}" "Google Analytics: Failed to find report in response. Reason: #{inspect(body)}"
) )
Sentry.Context.set_extra_context(%{google_analytics_response: response}) {:error, {:invalid_response, body}}
{:error, {:invalid_response, response}}
end end
end end
@ -114,13 +97,19 @@ defmodule Plausible.Google.HTTP do
url = "#{api_url()}/webmasters/v3/sites" url = "#{api_url()}/webmasters/v3/sites"
headers = [{"Content-Type", "application/json"}, {"Authorization", "Bearer #{access_token}"}] headers = [{"Content-Type", "application/json"}, {"Authorization", "Bearer #{access_token}"}]
case HTTPClient.get(url, headers) do case HTTPClient.impl().get(url, headers) do
{:ok, %{body: body}} -> {:ok, %{body: body}} ->
{:ok, body} {:ok, body}
{:error, reason} = e -> {:error, %{reason: %{status: s}}} when s in [401, 403] ->
{:error, "google_auth_error"}
{:error, %{reason: %{body: %{"error" => error}}}} ->
{:error, error}
{:error, reason} ->
Logger.error("Google Analytics: failed to list sites: #{inspect(reason)}") Logger.error("Google Analytics: failed to list sites: #{inspect(reason)}")
e {:error, "failed_to_list_sites"}
end end
end end
@ -182,25 +171,20 @@ defmodule Plausible.Google.HTTP do
url = "#{api_url()}/webmasters/v3/sites/#{property}/searchAnalytics/query" url = "#{api_url()}/webmasters/v3/sites/#{property}/searchAnalytics/query"
headers = [{"Authorization", "Bearer #{access_token}"}] headers = [{"Authorization", "Bearer #{access_token}"}]
case HTTPClient.post(url, headers, params) do case HTTPClient.impl().post(url, headers, params) do
{:ok, %Finch.Response{body: body, status: 200}} -> {:ok, %Finch.Response{body: body, status: 200}} ->
{:ok, body} {:ok, body}
{:error, %{reason: %Finch.Response{body: body, status: 401}}} -> {:error, %{reason: %Finch.Response{body: _body, status: status}}}
Sentry.capture_message("Error fetching Google queries", extra: %{body: inspect(body)}) when status in [401, 403] ->
{:error, :invalid_credentials} {:error, "google_auth_error"}
{:error, %{reason: %Finch.Response{body: body, status: 403}}} -> {:error, %{reason: %{body: %{"error" => error}}}} ->
Sentry.capture_message("Error fetching Google queries", extra: %{body: inspect(body)}) {:error, error}
{:error, get_in(body, ["error", "message"])}
{:error, %{reason: %Finch.Response{body: body}}} -> {:error, reason} ->
Sentry.capture_message("Error fetching Google queries", extra: %{body: inspect(body)}) Logger.error("Google Analytics: failed to list stats: #{inspect(reason)}")
{:error, :unknown} {:error, "failed_to_list_stats"}
{:error, %{reason: _} = e} ->
Sentry.capture_message("Error fetching Google queries", extra: %{error: inspect(e)})
{:error, :unknown}
end end
end end
@ -223,14 +207,12 @@ defmodule Plausible.Google.HTTP do
{:ok, %Finch.Response{body: body, status: 200}} -> {:ok, %Finch.Response{body: body, status: 200}} ->
{:ok, body} {:ok, body}
{:error, %{reason: %Finch.Response{body: body, status: _non_http_200}}} -> {:error, %{reason: %Finch.Response{body: %{"error" => error}, status: _non_http_200}}} ->
body {:error, error}
|> Map.get("error")
|> then(&{:error, &1})
{:error, %{reason: _} = e} -> {:error, %{reason: _} = e} ->
Sentry.capture_message("Error fetching Google queries", extra: %{error: inspect(e)}) Sentry.capture_message("Error fetching Google queries", extra: %{error: inspect(e)})
{:error, :unknown} {:error, :unknown_error}
end end
end end

View File

@ -11,6 +11,7 @@ defmodule Plausible.HTTPClient.Non200Error do
end end
defmodule Plausible.HTTPClient.Interface do defmodule Plausible.HTTPClient.Interface do
@type finch_request_opts() :: Keyword.t()
@type url() :: Finch.Request.url() @type url() :: Finch.Request.url()
@type headers() :: Finch.Request.headers() @type headers() :: Finch.Request.headers()
@type params() :: Finch.Request.body() | map() @type params() :: Finch.Request.body() | map()
@ -21,6 +22,7 @@ defmodule Plausible.HTTPClient.Interface do
@callback get(url(), headers()) :: response() @callback get(url(), headers()) :: response()
@callback get(url()) :: response() @callback get(url()) :: response()
@callback post(url(), headers(), params()) :: response() @callback post(url(), headers(), params()) :: response()
@callback post(url(), headers(), params(), finch_request_opts()) :: response()
end end
defmodule Plausible.HTTPClient do defmodule Plausible.HTTPClient do
@ -40,8 +42,8 @@ defmodule Plausible.HTTPClient do
@behaviour Plausible.HTTPClient.Interface @behaviour Plausible.HTTPClient.Interface
@impl Plausible.HTTPClient.Interface @impl Plausible.HTTPClient.Interface
def post(url, headers \\ [], params \\ nil) do def post(url, headers \\ [], params \\ nil, finch_req_opts \\ []) do
call(:post, url, headers, params) call(:post, url, headers, params, finch_req_opts)
end end
@doc """ @doc """
@ -59,12 +61,12 @@ defmodule Plausible.HTTPClient do
Application.get_env(:plausible, :http_impl, __MODULE__) Application.get_env(:plausible, :http_impl, __MODULE__)
end end
defp call(method, url, headers, params) do defp call(method, url, headers, params, finch_req_opts \\ []) do
{params, headers} = maybe_encode_params(params, headers) {params, headers} = maybe_encode_params(params, headers)
method method
|> build_request(url, headers, params) |> build_request(url, headers, params)
|> do_request() |> do_request(finch_req_opts)
|> maybe_decode_body() |> maybe_decode_body()
|> tag_error() |> tag_error()
end end
@ -73,8 +75,8 @@ defmodule Plausible.HTTPClient do
Finch.build(method, url, headers, params) Finch.build(method, url, headers, params)
end end
defp do_request(request) do defp do_request(request, finch_req_opts) do
Finch.request(request, Plausible.Finch) Finch.request(request, Plausible.Finch, finch_req_opts)
end end
defp maybe_encode_params(params, headers) when is_binary(params) or is_nil(params) do defp maybe_encode_params(params, headers) when is_binary(params) or is_nil(params) do
@ -123,7 +125,7 @@ defmodule Plausible.HTTPClient do
end end
end end
defp maybe_decode_body(resp), do: resp defp maybe_decode_body(response), do: response
defp json?(headers) do defp json?(headers) do
found = found =

View File

@ -429,18 +429,24 @@ defmodule PlausibleWeb.Api.StatsController do
%{:visitors => %{value: total_visitors}} = Stats.aggregate(site, query, [:visitors]) %{:visitors => %{value: total_visitors}} = Stats.aggregate(site, query, [:visitors])
user_id = get_session(conn, :current_user_id)
is_admin = user_id && Plausible.Sites.has_admin_access?(user_id, site)
case search_terms do case search_terms do
nil -> nil ->
user_id = get_session(conn, :current_user_id)
is_admin = user_id && Plausible.Sites.has_admin_access?(user_id, site)
json(conn, %{not_configured: true, is_admin: is_admin, total_visitors: total_visitors}) json(conn, %{not_configured: true, is_admin: is_admin, total_visitors: total_visitors})
{:ok, terms} -> {:ok, terms} ->
json(conn, %{search_terms: terms, total_visitors: total_visitors}) json(conn, %{search_terms: terms, total_visitors: total_visitors})
{:error, e} -> {:error, _} ->
put_status(conn, 500) conn
|> json(%{error: e}) |> put_status(502)
|> json(%{
not_configured: true,
is_admin: is_admin,
total_visitors: total_visitors
})
end end
end end

View File

@ -36,12 +36,24 @@
<%= submit "Save", class: "button" %> <%= submit "Save", class: "button" %>
<% end %> <% end %>
<% {:error, error} -> %> <% {:error, error} -> %>
<p class="text-gray-700 dark:text-gray-300 mt-6">The following error happened when fetching your Google Search Console domains.</p> <p class="text-gray-700 dark:text-gray-300 mt-6">The following error happened when fetching your Google Search Console domains:</p>
<%= if error == "invalid_grant" do %> <%= case error do %>
<p class="text-red-700 font-medium mt-3"><a href="https://plausible.io/docs/google-search-console-integration#i-get-the-invalid-grant-error">Invalid Grant error returned from Google. <span class="text-indigo-500">See here on how to fix it.</a></p> <% "invalid_grant" -> %>
<% else %> <p class="text-red-700 font-medium mt-3">
<p class="text-red-700 font-medium mt-3">Something went wrong</p> <a href="https://plausible.io/docs/google-search-console-integration#i-get-the-invalid-grant-error">
Invalid Grant error returned from Google. <span class="text-indigo-500">See here on how to fix it</span>.
</a>
</p>
<% "google_auth_error" -> %>
<p class="text-red-700 font-medium mt-3">
Your Search Console account hasn't been connected successfully. Please unlink your Google account and try linking it again.
</p>
<% _ -> %>
<p class="text-red-700 font-medium mt-3">
Something went wrong, but looks temporary. If the problem persists, try re-linking your Google account.
</p>
<% end %> <% end %>
<% end %> <% end %>
<% else %> <% else %>

View File

@ -2,7 +2,6 @@ defmodule Plausible.BillingTest do
use Plausible.DataCase use Plausible.DataCase
use Bamboo.Test, shared: true use Bamboo.Test, shared: true
alias Plausible.Billing alias Plausible.Billing
import Plausible.TestUtils
describe "usage" do describe "usage" do
test "is 0 with no events" do test "is 0 with no events" do

View File

@ -2,13 +2,16 @@ defmodule Plausible.Google.ApiTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
use ExVCR.Mock, adapter: ExVCR.Adapter.Finch use ExVCR.Mock, adapter: ExVCR.Adapter.Finch
alias Plausible.Google.Api alias Plausible.Google.Api
import Plausible.TestUtils
import Double import ExUnit.CaptureLog
import Mox
setup :verify_on_exit!
setup [:create_user, :create_new_site] setup [:create_user, :create_new_site]
describe "fetch_and_persist/4" do describe "fetch_and_persist/4" do
@ok_response File.read!("fixture/ga_batch_report.json") @ok_response Jason.decode!(File.read!("fixture/ga_batch_report.json"))
@no_report_response Jason.decode!(File.read!("fixture/ga_report_empty_rows.json"))
setup do setup do
{:ok, pid} = Plausible.Google.Buffer.start_link() {:ok, pid} = Plausible.Google.Buffer.start_link()
@ -16,12 +19,6 @@ defmodule Plausible.Google.ApiTest do
end end
test "will fetch and persist import data from Google Analytics", %{site: site, buffer: buffer} do test "will fetch and persist import data from Google Analytics", %{site: site, buffer: buffer} do
finch_double =
Finch
|> stub(:request, fn _, _, _ ->
{:ok, %Finch.Response{status: 200, body: @ok_response}}
end)
request = %Plausible.Google.ReportRequest{ request = %Plausible.Google.ReportRequest{
dataset: "imported_exit_pages", dataset: "imported_exit_pages",
view_id: "123", view_id: "123",
@ -33,8 +30,36 @@ defmodule Plausible.Google.ApiTest do
page_size: 10_000 page_size: 10_000
} }
expect(
Plausible.HTTPClient.Mock,
:post,
fn
"https://analyticsreporting.googleapis.com/v4/reports:batchGet",
[{"Authorization", "Bearer fake-token"}],
%{
reportRequests: [
%{
dateRanges: [%{endDate: ~D[2022-02-01], startDate: ~D[2022-01-01]}],
dimensions: [
%{histogramBuckets: [], name: "ga:date"},
%{histogramBuckets: [], name: "ga:exitPagePath"}
],
hideTotals: true,
hideValueRanges: true,
metrics: [%{expression: "ga:users"}, %{expression: "ga:exits"}],
orderBys: [%{fieldName: "ga:date", sortOrder: "DESCENDING"}],
pageSize: 10000,
pageToken: nil,
viewId: "123"
}
]
},
[receive_timeout: 60_000] ->
{:ok, %Finch.Response{status: 200, body: @ok_response}}
end
)
Api.fetch_and_persist(site, request, Api.fetch_and_persist(site, request,
http_client: finch_double,
sleep_time: 0, sleep_time: 0,
buffer: buffer buffer: buffer
) )
@ -52,13 +77,21 @@ defmodule Plausible.Google.ApiTest do
site: site, site: site,
buffer: buffer buffer: buffer
} do } do
finch_double = expect(
Finch Plausible.HTTPClient.Mock,
|> stub(:request, fn _, _, _ -> {:error, :timeout} end) :post,
|> stub(:request, fn _, _, _ -> {:error, :nx_domain} end) 5,
|> stub(:request, fn _, _, _ -> {:error, :closed} end) fn
|> stub(:request, fn _, _, _ -> {:ok, %Finch.Response{status: 503}} end) "https://analyticsreporting.googleapis.com/v4/reports:batchGet",
|> stub(:request, fn _, _, _ -> {:ok, %Finch.Response{status: 502}} end) _,
_,
[receive_timeout: 60_000] ->
Enum.random([
{:error, %Mint.TransportError{reason: :nxdomain}},
{:error, %{reason: %Finch.Response{status: 500}}}
])
end
)
request = %Plausible.Google.ReportRequest{ request = %Plausible.Google.ReportRequest{
view_id: "123", view_id: "123",
@ -72,25 +105,23 @@ defmodule Plausible.Google.ApiTest do
assert {:error, :request_failed} = assert {:error, :request_failed} =
Api.fetch_and_persist(site, request, Api.fetch_and_persist(site, request,
http_client: finch_double,
sleep_time: 0, sleep_time: 0,
buffer: buffer buffer: buffer
) )
assert_receive({Finch, :request, [_, _, [receive_timeout: 60_000]]})
assert_receive({Finch, :request, [_, _, [receive_timeout: 60_000]]})
assert_receive({Finch, :request, [_, _, [receive_timeout: 60_000]]})
assert_receive({Finch, :request, [_, _, [receive_timeout: 60_000]]})
assert_receive({Finch, :request, [_, _, [receive_timeout: 60_000]]})
end end
test "does not fail when report does not have rows key", %{site: site, buffer: buffer} do test "does not fail when report does not have rows key", %{site: site, buffer: buffer} do
finch_double = expect(
Finch Plausible.HTTPClient.Mock,
|> stub(:request, fn _, _, _ -> :post,
{:ok, fn
%Finch.Response{status: 200, body: File.read!("fixture/ga_report_empty_rows.json")}} "https://analyticsreporting.googleapis.com/v4/reports:batchGet",
end) _,
_,
[receive_timeout: 60_000] ->
{:ok, %Finch.Response{status: 200, body: @no_report_response}}
end
)
request = %Plausible.Google.ReportRequest{ request = %Plausible.Google.ReportRequest{
dataset: "imported_exit_pages", dataset: "imported_exit_pages",
@ -105,14 +136,92 @@ defmodule Plausible.Google.ApiTest do
assert :ok == assert :ok ==
Api.fetch_and_persist(site, request, Api.fetch_and_persist(site, request,
http_client: finch_double,
sleep_time: 0, sleep_time: 0,
buffer: buffer buffer: buffer
) )
end end
end end
describe "fetch_stats/3" do describe "fetch_stats/3 errors" do
setup %{user: user, site: site} do
insert(:google_auth,
user: user,
site: site,
property: "sc-domain:dummy.test",
expires: NaiveDateTime.add(NaiveDateTime.utc_now(), 3600)
)
:ok
end
test "returns generic google_auth_error on 401/403", %{site: site} do
expect(
Plausible.HTTPClient.Mock,
:post,
fn
"https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Adummy.test/searchAnalytics/query",
[{"Authorization", "Bearer 123"}],
%{
dimensionFilterGroups: %{},
dimensions: ["query"],
endDate: "2022-01-05",
rowLimit: 5,
startDate: "2022-01-01"
} ->
{:error, %{reason: %Finch.Response{status: Enum.random([401, 403])}}}
end
)
query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])}
assert {:error, "google_auth_error"} = Plausible.Google.Api.fetch_stats(site, query, 5)
end
test "returns whatever error code google returns on API client error", %{site: site} do
expect(
Plausible.HTTPClient.Mock,
:post,
fn
"https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Adummy.test/searchAnalytics/query",
_,
_ ->
{:error, %{reason: %Finch.Response{status: 400, body: %{"error" => "some_error"}}}}
end
)
query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])}
assert {:error, "some_error"} = Plausible.Google.Api.fetch_stats(site, query, 5)
end
test "returns generic HTTP error and logs it", %{site: site} do
expect(
Plausible.HTTPClient.Mock,
:post,
fn
"https://www.googleapis.com/webmasters/v3/sites/sc-domain%3Adummy.test/searchAnalytics/query",
_,
_ ->
{:error, Finch.Error.exception(:some_reason)}
end
)
query = %Plausible.Stats.Query{date_range: Date.range(~D[2022-01-01], ~D[2022-01-05])}
log =
capture_log(fn ->
assert {:error, "failed_to_list_stats"} =
Plausible.Google.Api.fetch_stats(site, query, 5)
end)
assert log =~ "Google Analytics: failed to list stats: %Finch.Error{reason: :some_reason}"
end
end
describe "fetch_stats/3 with VCR cassetes" do
# We need real HTTP Client for VCR tests
setup_patch_env(:http_impl, Plausible.HTTPClient)
test "returns name and visitor count", %{user: user, site: site} do test "returns name and visitor count", %{user: user, site: site} do
use_cassette "google_analytics_stats", match_requests_on: [:request_body] do use_cassette "google_analytics_stats", match_requests_on: [:request_body] do
insert(:google_auth, insert(:google_auth,

View File

@ -1,6 +1,6 @@
defmodule Plausible.Google.BufferTest do defmodule Plausible.Google.BufferTest do
use Plausible.DataCase, async: false use Plausible.DataCase, async: false
import Plausible.TestUtils
import Ecto.Query import Ecto.Query
alias Plausible.Google.Buffer alias Plausible.Google.Buffer
@ -8,10 +8,7 @@ defmodule Plausible.Google.BufferTest do
defp set_buffer_size(_setup_args) do defp set_buffer_size(_setup_args) do
google_setting = Application.get_env(:plausible, :google) google_setting = Application.get_env(:plausible, :google)
on_exit(fn -> Application.put_env(:plausible, :google, google_setting) end) patch_env(:google, Keyword.put(google_setting, :max_buffer_size, 10))
test_setting = Keyword.put(google_setting, :max_buffer_size, 10)
Application.put_env(:plausible, :google, test_setting)
:ok :ok
end end

View File

@ -2,9 +2,10 @@ defmodule Plausible.Google.Api.VCRTest do
use Plausible.DataCase, async: false use Plausible.DataCase, async: false
use ExVCR.Mock, adapter: ExVCR.Adapter.Finch use ExVCR.Mock, adapter: ExVCR.Adapter.Finch
require Ecto.Query require Ecto.Query
import Plausible.TestUtils
setup [:create_user, :create_site] setup [:create_user, :create_site]
# We need real HTTP Client for VCR tests
setup_patch_env(:http_impl, Plausible.HTTPClient)
test "imports page views from Google Analytics", %{site: site} do test "imports page views from Google Analytics", %{site: site} do
use_cassette "google_analytics_import#1", match_requests_on: [:request_body] do use_cassette "google_analytics_import#1", match_requests_on: [:request_body] do

View File

@ -102,6 +102,18 @@ defmodule Plausible.HTTPClientTest do
HTTPClient.post(bypass_url(bypass, path: "/any"), headers_no_content_type, params) HTTPClient.post(bypass_url(bypass, path: "/any"), headers_no_content_type, params)
end end
test "post/4 accepts finch request opts", %{bypass: bypass} do
Bypass.expect_once(bypass, "POST", "/timeout", fn conn ->
Process.sleep(500)
Conn.resp(conn, 200, "ok")
end)
assert {:error, %Mint.TransportError{reason: :timeout}} ==
HTTPClient.post(bypass_url(bypass, path: "/timeout"), [], %{}, receive_timeout: 100)
Bypass.down(bypass)
end
test "non-200 responses are tagged as errors", %{bypass: bypass} do test "non-200 responses are tagged as errors", %{bypass: bypass} do
Bypass.expect_once(bypass, "GET", "/get", fn conn -> Bypass.expect_once(bypass, "GET", "/get", fn conn ->
Conn.resp(conn, 300, "oops") Conn.resp(conn, 300, "oops")

View File

@ -1,7 +1,6 @@
defmodule Plausible.ImportedTest do defmodule Plausible.ImportedTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Timex use Timex
import Plausible.TestUtils
@user_id 123 @user_id 123

View File

@ -1,6 +1,5 @@
defmodule Plausible.PurgeTest do defmodule Plausible.PurgeTest do
use Plausible.DataCase use Plausible.DataCase
import Plausible.TestUtils
setup do setup do
site = insert(:site, stats_start_date: ~D[2020-01-01]) site = insert(:site, stats_start_date: ~D[2020-01-01])

View File

@ -1,6 +1,6 @@
defmodule Plausible.SiteAdminTest do defmodule Plausible.SiteAdminTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
import Plausible.TestUtils
alias Plausible.{SiteAdmin, ClickhouseRepo, ClickhouseEvent, ClickhouseSession} alias Plausible.{SiteAdmin, ClickhouseRepo, ClickhouseEvent, ClickhouseSession}
describe "transfer_data" do describe "transfer_data" do

View File

@ -1,6 +1,6 @@
defmodule Plausible.SitesTest do defmodule Plausible.SitesTest do
use Plausible.DataCase use Plausible.DataCase
import Plausible.TestUtils
alias Plausible.Sites alias Plausible.Sites
describe "is_member?" do describe "is_member?" do

View File

@ -50,14 +50,7 @@ defmodule PlausibleWeb.CaptchaTest do
end end
describe "with patched application env" do describe "with patched application env" do
setup do setup_patch_env(:hcaptcha, sitekey: nil)
original_env = Application.get_env(:plausible, :hcaptcha)
Application.put_env(:plausible, :hcaptcha, sitekey: nil)
on_exit(fn ->
Application.put_env(:plausible, :hcaptcha, original_env)
end)
end
test "returns true when disabled" do test "returns true when disabled" do
assert Captcha.verify("disabled") assert Captcha.verify("disabled")

View File

@ -1,7 +1,6 @@
defmodule PlausibleWeb.Api.ExternalSitesControllerTest do defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Plausible.Repo use Plausible.Repo
import Plausible.TestUtils
setup %{conn: conn} do setup %{conn: conn} do
user = insert(:user) user = insert(:user)

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.ExternalStatsController.AggregateTest do defmodule PlausibleWeb.Api.ExternalStatsController.AggregateTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
setup [:create_user, :create_new_site, :create_api_key, :use_api_key] setup [:create_user, :create_new_site, :create_api_key, :use_api_key]
@user_id 123 @user_id 123

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.ExternalStatsController.AuthTest do defmodule PlausibleWeb.Api.ExternalStatsController.AuthTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
setup [:create_user, :create_api_key] setup [:create_user, :create_api_key]
@ -69,12 +68,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController.AuthTest do
describe "super admin access" do describe "super admin access" do
setup %{user: user} do setup %{user: user} do
original_env = Application.get_env(:plausible, :super_admin_user_ids) patch_env(:super_admin_user_ids, [user.id])
Application.put_env(:plausible, :super_admin_user_ids, [user.id])
on_exit(fn ->
Application.put_env(:plausible, :super_admin_user_ids, original_env)
end)
end end
test "can access as a super admin", %{conn: conn, api_key: api_key} do test "can access as a super admin", %{conn: conn, api_key: api_key} do

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 1231 @user_id 1231
setup [:create_user, :create_new_site, :create_api_key, :use_api_key] setup [:create_user, :create_new_site, :create_api_key, :use_api_key]

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.ExternalStatsController.TimeseriesTest do defmodule PlausibleWeb.Api.ExternalStatsController.TimeseriesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
setup [:create_user, :create_new_site, :create_api_key, :use_api_key] setup [:create_user, :create_new_site, :create_api_key, :use_api_key]

View File

@ -1,7 +1,6 @@
defmodule PlausibleWeb.Api.InternalControllerTest do defmodule PlausibleWeb.Api.InternalControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Plausible.Repo use Plausible.Repo
import Plausible.TestUtils
describe "GET /api/:domain/status" do describe "GET /api/:domain/status" do
setup [:create_user, :log_in] setup [:create_user, :log_in]

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.AuthorizationTest do defmodule PlausibleWeb.Api.StatsController.AuthorizationTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "API authorization - as anonymous user" do describe "API authorization - as anonymous user" do
test "Sends 404 Not found for a site that doesn't exist", %{conn: conn} do test "Sends 404 Not found for a site that doesn't exist", %{conn: conn} do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.BrowsersTest do defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/browsers" do describe "GET /api/stats/:domain/browsers" do
setup [:create_user, :log_in, :create_new_site, :add_imported_data] setup [:create_user, :log_in, :create_new_site, :add_imported_data]

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.CitiesTest do defmodule PlausibleWeb.Api.StatsController.CitiesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/cities" do describe "GET /api/stats/:domain/cities" do
defp seed(%{site: site}) do defp seed(%{site: site}) do

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.StatsController.ConversionsTest do defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 123 @user_id 123
describe "GET /api/stats/:domain/conversions" do describe "GET /api/stats/:domain/conversions" do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.CountriesTest do defmodule PlausibleWeb.Api.StatsController.CountriesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/countries" do describe "GET /api/stats/:domain/countries" do
setup [:create_user, :log_in, :create_new_site, :add_imported_data] setup [:create_user, :log_in, :create_new_site, :add_imported_data]

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.CurrentVisitorsTest do defmodule PlausibleWeb.Api.StatsController.CurrentVisitorsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/current-visitors" do describe "GET /api/stats/:domain/current-visitors" do
setup [:create_user, :log_in, :create_site] setup [:create_user, :log_in, :create_site]

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.StatsController.MainGraphTest do defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 123 @user_id 123
describe "GET /api/stats/main-graph - plot" do describe "GET /api/stats/main-graph - plot" do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/operating_systems" do describe "GET /api/stats/:domain/operating_systems" do
setup [:create_user, :log_in, :create_new_site, :add_imported_data] setup [:create_user, :log_in, :create_new_site, :add_imported_data]

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.StatsController.PagesTest do defmodule PlausibleWeb.Api.StatsController.PagesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 123 @user_id 123
describe "GET /api/stats/:domain/pages" do describe "GET /api/stats/:domain/pages" do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.RegionsTest do defmodule PlausibleWeb.Api.StatsController.RegionsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/regions" do describe "GET /api/stats/:domain/regions" do
defp seed(%{site: site}) do defp seed(%{site: site}) do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/browsers" do describe "GET /api/stats/:domain/browsers" do
setup [:create_user, :log_in, :create_new_site, :add_imported_data] setup [:create_user, :log_in, :create_new_site, :add_imported_data]

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.StatsController.SourcesTest do defmodule PlausibleWeb.Api.StatsController.SourcesTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 123 @user_id 123
describe "GET /api/stats/:domain/sources" do describe "GET /api/stats/:domain/sources" do

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.Api.StatsController.SuggestionsTest do defmodule PlausibleWeb.Api.StatsController.SuggestionsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /api/stats/:domain/suggestions/:filter_name" do describe "GET /api/stats/:domain/suggestions/:filter_name" do
setup [:create_user, :log_in, :create_site] setup [:create_user, :log_in, :create_site]

View File

@ -1,6 +1,6 @@
defmodule PlausibleWeb.Api.StatsController.TopStatsTest do defmodule PlausibleWeb.Api.StatsController.TopStatsTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
@user_id 123 @user_id 123
describe "GET /api/stats/top-stats - default" do describe "GET /api/stats/top-stats - default" do

View File

@ -2,7 +2,6 @@ defmodule PlausibleWeb.AuthControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Bamboo.Test use Bamboo.Test
use Plausible.Repo use Plausible.Repo
import Plausible.TestUtils
import Mox import Mox
setup :verify_on_exit! setup :verify_on_exit!

View File

@ -1,6 +1,5 @@
defmodule PlausibleWeb.BillingControllerTest do defmodule PlausibleWeb.BillingControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
import Plausible.TestUtils
describe "GET /upgrade" do describe "GET /upgrade" do
setup [:create_user, :log_in] setup [:create_user, :log_in]

View File

@ -2,7 +2,6 @@ defmodule PlausibleWeb.Site.InvitationControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Plausible.Repo use Plausible.Repo
use Bamboo.Test use Bamboo.Test
import Plausible.TestUtils
setup [:create_user, :log_in] setup [:create_user, :log_in]

View File

@ -2,7 +2,6 @@ defmodule PlausibleWeb.Site.MembershipControllerTest do
use PlausibleWeb.ConnCase use PlausibleWeb.ConnCase
use Plausible.Repo use Plausible.Repo
use Bamboo.Test use Bamboo.Test
import Plausible.TestUtils
setup [:create_user, :log_in] setup [:create_user, :log_in]

View File

@ -3,7 +3,10 @@ defmodule PlausibleWeb.SiteControllerTest do
use Plausible.Repo use Plausible.Repo
use Bamboo.Test use Bamboo.Test
use Oban.Testing, repo: Plausible.Repo use Oban.Testing, repo: Plausible.Repo
import Plausible.TestUtils
import ExUnit.CaptureLog
import Mox
setup :verify_on_exit!
describe "GET /sites/new" do describe "GET /sites/new" do
setup [:create_user, :log_in] setup [:create_user, :log_in]
@ -384,6 +387,118 @@ defmodule PlausibleWeb.SiteControllerTest do
end end
end end
describe "GET /:webiste/settings/search-console for self-hosting" do
setup [:create_user, :log_in, :create_site]
test "display search console settings", %{conn: conn, site: site} do
conn = get(conn, "/#{site.domain}/settings/search-console")
resp = html_response(conn, 200)
assert resp =~ "An extra step is needed"
assert resp =~ "Google Search Console integration"
assert resp =~ "self-hosting-configuration"
end
end
describe "GET /:webiste/settings/search-console" do
setup [:create_user, :log_in, :create_site]
setup_patch_env(:google, client_id: "some", api_url: "https://www.googleapis.com")
setup %{site: site, user: user} = context do
insert(:google_auth, user: user, site: site, property: "sc-domain:#{site.domain}")
context
end
test "displays appropriate error in case of google account `google_auth_error`", %{
conn: conn,
site: site
} do
expect(
Plausible.HTTPClient.Mock,
:get,
fn
"https://www.googleapis.com/webmasters/v3/sites",
[{"Content-Type", "application/json"}, {"Authorization", "Bearer 123"}] ->
{:error, %{reason: %Finch.Response{status: Enum.random([401, 403])}}}
end
)
conn = get(conn, "/#{site.domain}/settings/search-console")
resp = html_response(conn, 200)
assert resp =~ "Your Search Console account hasn't been connected successfully"
assert resp =~ "Please unlink your Google account and try linking it again"
end
test "displays docs link error in case of `invalid_grant`", %{
conn: conn,
site: site
} do
expect(
Plausible.HTTPClient.Mock,
:get,
fn
"https://www.googleapis.com/webmasters/v3/sites",
[{"Content-Type", "application/json"}, {"Authorization", "Bearer 123"}] ->
{:error, %{reason: %Finch.Response{status: 400, body: %{"error" => "invalid_grant"}}}}
end
)
conn = get(conn, "/#{site.domain}/settings/search-console")
resp = html_response(conn, 200)
assert resp =~
"https://plausible.io/docs/google-search-console-integration#i-get-the-invalid-grant-error"
end
test "displays generic error in case of random error code returned by google", %{
conn: conn,
site: site
} do
expect(
Plausible.HTTPClient.Mock,
:get,
fn
"https://www.googleapis.com/webmasters/v3/sites",
[{"Content-Type", "application/json"}, {"Authorization", "Bearer 123"}] ->
{:error, %{reason: %Finch.Response{status: 503, body: %{"error" => "some_error"}}}}
end
)
conn = get(conn, "/#{site.domain}/settings/search-console")
resp = html_response(conn, 200)
assert resp =~ "Something went wrong, but looks temporary"
assert resp =~ "try re-linking your Google account"
end
test "displays generic error and logs a message, in case of random HTTP failure calling google",
%{
conn: conn,
site: site
} do
expect(
Plausible.HTTPClient.Mock,
:get,
fn
"https://www.googleapis.com/webmasters/v3/sites",
[{"Content-Type", "application/json"}, {"Authorization", "Bearer 123"}] ->
{:error, :nxdomain}
end
)
log =
capture_log(fn ->
conn = get(conn, "/#{site.domain}/settings/search-console")
resp = html_response(conn, 200)
assert resp =~ "Something went wrong, but looks temporary"
assert resp =~ "try re-linking your Google account"
end)
assert log =~ "Google Analytics: failed to list sites: :nxdomain"
end
end
describe "GET /:website/goals/new" do describe "GET /:website/goals/new" do
setup [:create_user, :log_in, :create_site] setup [:create_user, :log_in, :create_site]

View File

@ -1,7 +1,6 @@
defmodule PlausibleWeb.StatsControllerTest do defmodule PlausibleWeb.StatsControllerTest do
use PlausibleWeb.ConnCase, async: true use PlausibleWeb.ConnCase, async: true
use Plausible.Repo use Plausible.Repo
import Plausible.TestUtils
describe "GET /:website - anonymous user" do describe "GET /:website - anonymous user" do
test "public site - shows site stats", %{conn: conn} do test "public site - shows site stats", %{conn: conn} do

View File

@ -2,7 +2,6 @@ defmodule PlausibleWeb.AuthorizeSiteAccessTest do
use PlausibleWeb.ConnCase, async: true use PlausibleWeb.ConnCase, async: true
alias PlausibleWeb.AuthorizeSiteAccess alias PlausibleWeb.AuthorizeSiteAccess
import Plausible.TestUtils
setup [:create_user, :log_in] setup [:create_user, :log_in]
test "doesn't allow :website bypass with :domain in body", %{conn: conn, user: me} do test "doesn't allow :website bypass with :domain in body", %{conn: conn, user: me} do

View File

@ -18,6 +18,7 @@ defmodule PlausibleWeb.ConnCase do
using do using do
quote do quote do
# Import conveniences for testing with connections # Import conveniences for testing with connections
use Plausible.TestUtils
import Plug.Conn import Plug.Conn
import Phoenix.ConnTest import Phoenix.ConnTest
alias PlausibleWeb.Router.Helpers, as: Routes alias PlausibleWeb.Router.Helpers, as: Routes

View File

@ -17,6 +17,7 @@ defmodule Plausible.DataCase do
using do using do
quote do quote do
use Plausible.Repo use Plausible.Repo
use Plausible.TestUtils
import Ecto.Changeset import Ecto.Changeset
import Plausible.DataCase import Plausible.DataCase

View File

@ -2,6 +2,34 @@ defmodule Plausible.TestUtils do
use Plausible.Repo use Plausible.Repo
alias Plausible.Factory alias Plausible.Factory
defmacro __using__(_) do
quote do
require Plausible.TestUtils
import Plausible.TestUtils
end
end
defmacro patch_env(env_key, value) do
quote do
original_env = Application.get_env(:plausible, unquote(env_key))
Application.put_env(:plausible, unquote(env_key), unquote(value))
on_exit(fn ->
Application.put_env(:plausible, unquote(env_key), original_env)
end)
{:ok, %{patched_env: true}}
end
end
defmacro setup_patch_env(env_key, value) do
quote do
setup do
patch_env(unquote(env_key), unquote(value))
end
end
end
def create_user(_) do def create_user(_) do
{:ok, user: Factory.insert(:user)} {:ok, user: Factory.insert(:user)}
end end

View File

@ -2,7 +2,7 @@ defmodule Plausible.Workers.CheckUsageTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
use Bamboo.Test use Bamboo.Test
import Double import Double
import Plausible.TestUtils
alias Plausible.Workers.CheckUsage alias Plausible.Workers.CheckUsage
setup [:create_user, :create_site] setup [:create_user, :create_site]

View File

@ -2,7 +2,7 @@ defmodule Plausible.Workers.ImportGoogleAnalyticsTest do
use Plausible.DataCase use Plausible.DataCase
use Bamboo.Test use Bamboo.Test
import Double import Double
import Plausible.TestUtils
alias Plausible.Workers.ImportGoogleAnalytics alias Plausible.Workers.ImportGoogleAnalytics
@imported_data %Plausible.Site.ImportedData{ @imported_data %Plausible.Site.ImportedData{

View File

@ -1,7 +1,7 @@
defmodule Plausible.Workers.NotifyAnnualRenewalTest do defmodule Plausible.Workers.NotifyAnnualRenewalTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
use Bamboo.Test use Bamboo.Test
import Plausible.TestUtils
alias Plausible.Workers.NotifyAnnualRenewal alias Plausible.Workers.NotifyAnnualRenewal
setup [:create_user, :create_site] setup [:create_user, :create_site]

View File

@ -1,5 +1,4 @@
defmodule Plausible.Workers.SendEmailReportTest do defmodule Plausible.Workers.SendEmailReportTest do
import Plausible.TestUtils
use Plausible.DataCase use Plausible.DataCase
use Bamboo.Test use Bamboo.Test
use Oban.Testing, repo: Plausible.Repo use Oban.Testing, repo: Plausible.Repo

View File

@ -2,7 +2,7 @@ defmodule Plausible.Workers.SendSiteSetupEmailsTest do
use Plausible.DataCase, async: true use Plausible.DataCase, async: true
use Bamboo.Test use Bamboo.Test
use Oban.Testing, repo: Plausible.Repo use Oban.Testing, repo: Plausible.Repo
import Plausible.TestUtils
alias Plausible.Workers.SendSiteSetupEmails alias Plausible.Workers.SendSiteSetupEmails
describe "when user has not managed to set up the site" do describe "when user has not managed to set up the site" do

View File

@ -2,7 +2,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
use Plausible.DataCase use Plausible.DataCase
use Bamboo.Test use Bamboo.Test
use Oban.Testing, repo: Plausible.Repo use Oban.Testing, repo: Plausible.Repo
import Plausible.TestUtils
alias Plausible.Workers.SendTrialNotifications alias Plausible.Workers.SendTrialNotifications
test "does not send a notification if user didn't create a site" do test "does not send a notification if user didn't create a site" do