| defmodule Plausible.Google.APITest do |
| use Plausible.DataCase, async: true |
| use Plausible.Test.Support.HTTPMocker |
|
|
| alias Plausible.Google |
| alias Plausible.Stats.{DateTimeRange, QueryBuilder} |
|
|
| import ExUnit.CaptureLog |
| import Mox |
| setup :verify_on_exit! |
|
|
| setup [:create_user, :create_site] |
|
|
| 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, |
| startRow: 0, |
| startDate: "2022-01-01" |
| } -> |
| {:error, %{reason: %Finch.Response{status: Enum.random([401, 403])}}} |
| end |
| ) |
|
|
| query = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]} |
| ) |
|
|
| assert {:error, "google_auth_error"} = Google.API.fetch_stats(site, query, {5, 0}, "") |
| 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 = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]} |
| ) |
|
|
| assert {:error, "some_error"} = Google.API.fetch_stats(site, query, {5, 0}, "") |
| 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 = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]} |
| ) |
|
|
| log = |
| capture_log(fn -> |
| assert {:error, "failed_to_list_stats"} = |
| Google.API.fetch_stats(site, query, {5, 0}, "") |
| end) |
|
|
| assert log =~ |
| "Google Search Console: failed to list stats: %Finch.Error{reason: :some_reason}" |
| end |
| end |
|
|
| test "returns error when token refresh fails", %{user: user, site: site} do |
| mock_http_with("google_auth#invalid_grant.json") |
|
|
| insert(:google_auth, |
| user: user, |
| site: site, |
| property: "sc-domain:dummy.test", |
| access_token: "*****", |
| refresh_token: "*****", |
| expires: NaiveDateTime.add(NaiveDateTime.utc_now(), -3600) |
| ) |
|
|
| query = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]} |
| ) |
|
|
| assert {:error, "invalid_grant"} = Google.API.fetch_stats(site, query, 5, "") |
| end |
|
|
| test "returns error when google auth not configured", %{site: site} do |
| time_range = DateTimeRange.new!(~U[2022-01-01 00:00:00Z], ~U[2022-01-05 23:59:59Z]) |
| query = %Plausible.Stats.Query{utc_time_range: time_range} |
|
|
| assert {:error, :google_property_not_configured} = Google.API.fetch_stats(site, query, 5, "") |
| end |
|
|
| describe "fetch_stats/3 with valid auth" 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 name and visitor count", %{site: site} do |
| mock_http_with("google_search_console.json") |
|
|
| query = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]} |
| ) |
|
|
| assert {:ok, |
| [ |
| %{name: "keyword1", visitors: 25, ctr: 36.8, impressions: 50, position: 2.2}, |
| %{name: "keyword3", visitors: 15} |
| ]} = Google.API.fetch_stats(site, query, {5, 0}, "") |
| end |
|
|
| test "transforms page filters to search console format", %{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: [ |
| %{filters: [%{expression: "https://dummy.test/page", dimension: "page"}]} |
| ], |
| dimensions: ["query"], |
| endDate: "2022-01-05", |
| rowLimit: 5, |
| startRow: 0, |
| startDate: "2022-01-01" |
| } -> |
| {:ok, %Finch.Response{status: 200, body: %{"rows" => []}}} |
| end |
| ) |
|
|
| query = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]}, |
| filters: [[:is, "event:page", ["/page"]]] |
| ) |
|
|
| assert {:ok, []} = Google.API.fetch_stats(site, query, {5, 0}, "") |
| end |
|
|
| test "returns :invalid filters when using filters that cannot be used in Search Console", %{ |
| site: site |
| } do |
| insert(:goal, event_name: "Signup", site: site) |
|
|
| query = |
| QueryBuilder.build!(site, |
| input_date_range: {:date_range, ~D[2022-01-01], ~D[2022-01-05]}, |
| filters: [[:is, "event:goal", ["Signup"]]] |
| ) |
|
|
| assert {:error, :unsupported_filters} = Google.API.fetch_stats(site, query, 5, "") |
| end |
| end |
| end |
|
|