2021-04-09 10:30:51 +03:00
|
|
|
defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
|
|
|
|
use PlausibleWeb.ConnCase
|
2021-06-16 15:00:07 +03:00
|
|
|
use Plausible.Repo
|
2021-04-09 10:30:51 +03:00
|
|
|
import Plausible.TestUtils
|
|
|
|
|
2021-04-09 11:53:41 +03:00
|
|
|
setup %{conn: conn} do
|
|
|
|
user = insert(:user)
|
|
|
|
api_key = insert(:api_key, user: user, scopes: ["sites:provision:*"])
|
|
|
|
conn = Plug.Conn.put_req_header(conn, "authorization", "Bearer #{api_key.key}")
|
|
|
|
{:ok, user: user, api_key: api_key, conn: conn}
|
|
|
|
end
|
2021-04-09 10:30:51 +03:00
|
|
|
|
|
|
|
describe "POST /api/v1/sites" do
|
|
|
|
test "can create a site", %{conn: conn} do
|
|
|
|
conn =
|
|
|
|
post(conn, "/api/v1/sites", %{
|
2021-04-14 15:04:25 +03:00
|
|
|
"domain" => "some-site.domain",
|
|
|
|
"timezone" => "Europe/Tallinn"
|
2021-04-09 10:30:51 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 200) == %{
|
|
|
|
"domain" => "some-site.domain",
|
|
|
|
"timezone" => "Europe/Tallinn"
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-04-15 11:03:06 +03:00
|
|
|
test "timezone defaults to Etc/UTC", %{conn: conn} do
|
2021-04-09 10:30:51 +03:00
|
|
|
conn =
|
|
|
|
post(conn, "/api/v1/sites", %{
|
2021-04-14 15:04:25 +03:00
|
|
|
"domain" => "some-site.domain"
|
2021-04-09 10:30:51 +03:00
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 200) == %{
|
|
|
|
"domain" => "some-site.domain",
|
2021-04-15 11:03:06 +03:00
|
|
|
"timezone" => "Etc/UTC"
|
2021-04-09 10:30:51 +03:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "domain is required", %{conn: conn} do
|
|
|
|
conn = post(conn, "/api/v1/sites", %{})
|
|
|
|
|
|
|
|
assert json_response(conn, 400) == %{
|
|
|
|
"error" => "domain can't be blank"
|
|
|
|
}
|
|
|
|
end
|
2021-04-09 11:53:41 +03:00
|
|
|
|
2021-05-05 10:39:24 +03:00
|
|
|
test "does not allow creating more sites than the limit", %{conn: conn, user: user} do
|
|
|
|
Application.put_env(:plausible, :site_limit, 3)
|
|
|
|
insert(:site, members: [user])
|
|
|
|
insert(:site, members: [user])
|
|
|
|
insert(:site, members: [user])
|
|
|
|
|
|
|
|
conn =
|
|
|
|
post(conn, "/api/v1/sites", %{
|
|
|
|
"domain" => "some-site.domain",
|
|
|
|
"timezone" => "Europe/Tallinn"
|
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 403) == %{
|
|
|
|
"error" =>
|
|
|
|
"Your account has reached the limit of 3 sites per account. Please contact hello@plausible.io to unlock more sites."
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-04-09 11:53:41 +03:00
|
|
|
test "cannot access with a bad API key scope", %{conn: conn, user: user} do
|
|
|
|
api_key = insert(:api_key, user: user, scopes: ["stats:read:*"])
|
|
|
|
|
|
|
|
conn =
|
|
|
|
conn
|
|
|
|
|> Plug.Conn.put_req_header("authorization", "Bearer #{api_key.key}")
|
|
|
|
|> post("/api/v1/sites", %{"site" => %{"domain" => "domain.com"}})
|
|
|
|
|
|
|
|
assert json_response(conn, 401) == %{
|
|
|
|
"error" =>
|
|
|
|
"Invalid API key. Please make sure you're using a valid API key with access to the resource you've requested."
|
|
|
|
}
|
|
|
|
end
|
2021-04-09 10:30:51 +03:00
|
|
|
end
|
|
|
|
|
2021-11-30 12:21:56 +03:00
|
|
|
describe "DELETE /api/v1/sites/:site_id" do
|
|
|
|
setup :create_new_site
|
|
|
|
|
|
|
|
test "delete a site by it's domain", %{conn: conn, site: site} do
|
|
|
|
conn = delete(conn, "/api/v1/sites/" <> site.domain)
|
|
|
|
|
|
|
|
assert json_response(conn, 200) == %{"deleted" => true}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "is 404 when site cannot be found", %{conn: conn} do
|
|
|
|
conn = delete(conn, "/api/v1/sites/foobar.baz")
|
|
|
|
|
|
|
|
assert json_response(conn, 404) == %{"error" => "Site could not be found"}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "cannot delete a site that the user does not own", %{conn: conn, user: user} do
|
|
|
|
site = insert(:site, members: [])
|
|
|
|
insert(:site_membership, user: user, site: site, role: :admin)
|
|
|
|
conn = delete(conn, "/api/v1/sites/" <> site.domain)
|
|
|
|
|
|
|
|
assert json_response(conn, 404) == %{"error" => "Site could not be found"}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "cannot access with a bad API key scope", %{conn: conn, site: site, user: user} do
|
|
|
|
api_key = insert(:api_key, user: user, scopes: ["stats:read:*"])
|
|
|
|
|
|
|
|
conn =
|
|
|
|
conn
|
|
|
|
|> Plug.Conn.put_req_header("authorization", "Bearer #{api_key.key}")
|
|
|
|
|> delete("/api/v1/sites/" <> site.domain)
|
|
|
|
|
|
|
|
assert json_response(conn, 401) == %{
|
|
|
|
"error" =>
|
|
|
|
"Invalid API key. Please make sure you're using a valid API key with access to the resource you've requested."
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-04-15 11:38:44 +03:00
|
|
|
describe "PUT /api/v1/sites/shared-links" do
|
2021-04-09 10:30:51 +03:00
|
|
|
setup :create_site
|
|
|
|
|
|
|
|
test "can add a shared link to a site", %{conn: conn, site: site} do
|
2021-04-15 14:05:28 +03:00
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
name: "Wordpress"
|
|
|
|
})
|
2021-04-09 10:30:51 +03:00
|
|
|
|
|
|
|
res = json_response(conn, 200)
|
|
|
|
assert res["name"] == "Wordpress"
|
|
|
|
assert String.starts_with?(res["url"], "http://")
|
|
|
|
end
|
|
|
|
|
|
|
|
test "is idempotent find or create op", %{conn: conn, site: site} do
|
2021-04-15 14:05:28 +03:00
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
name: "Wordpress"
|
|
|
|
})
|
2021-04-09 10:30:51 +03:00
|
|
|
|
|
|
|
%{"url" => url} = json_response(conn, 200)
|
|
|
|
|
2021-04-15 14:05:28 +03:00
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
name: "Wordpress"
|
|
|
|
})
|
2021-04-09 10:30:51 +03:00
|
|
|
|
|
|
|
assert %{"url" => ^url} = json_response(conn, 200)
|
|
|
|
end
|
2021-04-14 15:04:25 +03:00
|
|
|
|
|
|
|
test "returns 400 when site id missing", %{conn: conn} do
|
2021-04-15 14:05:28 +03:00
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
name: "Wordpress"
|
|
|
|
})
|
2021-04-14 15:04:25 +03:00
|
|
|
|
|
|
|
res = json_response(conn, 400)
|
2021-04-15 11:38:44 +03:00
|
|
|
assert res["error"] == "Parameter `site_id` is required to create a shared link"
|
2021-04-14 15:04:25 +03:00
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 404 when site id is non existent", %{conn: conn} do
|
2021-04-15 14:05:28 +03:00
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
name: "Wordpress",
|
|
|
|
site_id: "bad"
|
|
|
|
})
|
2021-04-14 15:04:25 +03:00
|
|
|
|
|
|
|
res = json_response(conn, 404)
|
|
|
|
assert res["error"] == "Site could not be found"
|
|
|
|
end
|
2021-06-16 15:00:07 +03:00
|
|
|
|
|
|
|
test "returns 404 when api key owner does not have permissions to create a shared link", %{
|
|
|
|
conn: conn,
|
|
|
|
site: site,
|
|
|
|
user: user
|
|
|
|
} do
|
|
|
|
Repo.update_all(
|
|
|
|
from(sm in Plausible.Site.Membership,
|
|
|
|
where: sm.site_id == ^site.id and sm.user_id == ^user.id
|
|
|
|
),
|
|
|
|
set: [role: :viewer]
|
|
|
|
)
|
|
|
|
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/shared-links", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
name: "Wordpress"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 404)
|
|
|
|
assert res["error"] == "Site could not be found"
|
|
|
|
end
|
2021-04-09 10:30:51 +03:00
|
|
|
end
|
2021-12-09 17:08:01 +03:00
|
|
|
|
|
|
|
describe "PUT /api/v1/sites/goals" do
|
|
|
|
setup :create_site
|
|
|
|
|
|
|
|
test "can add a goal as event to a site", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 200)
|
|
|
|
assert res["event_name"] == "Signup"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "can add a goal as page to a site", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "page",
|
|
|
|
page_path: "/signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 200)
|
|
|
|
assert res["page_path"] == "/signup"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "is idempotent find or create op", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
%{"goal_id" => goal_id} = json_response(conn, 200)
|
|
|
|
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
assert %{"goal_id" => ^goal_id} = json_response(conn, 200)
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 400 when site id missing", %{conn: conn} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 400)
|
|
|
|
assert res["error"] == "Parameter `site_id` is required to create a goal"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 404 when site id is non existent", %{conn: conn} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup",
|
|
|
|
site_id: "bad"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 404)
|
|
|
|
assert res["error"] == "Site could not be found"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 404 when api key owner does not have permissions to create a goal", %{
|
|
|
|
conn: conn,
|
|
|
|
site: site,
|
|
|
|
user: user
|
|
|
|
} do
|
|
|
|
Repo.update_all(
|
|
|
|
from(sm in Plausible.Site.Membership,
|
|
|
|
where: sm.site_id == ^site.id and sm.user_id == ^user.id
|
|
|
|
),
|
|
|
|
set: [role: :viewer]
|
|
|
|
)
|
|
|
|
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 404)
|
|
|
|
assert res["error"] == "Site could not be found"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 400 when goal type missing", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 400)
|
|
|
|
assert res["error"] == "Parameter `goal_type` is required to create a goal"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 400 when goal event name missing", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 400)
|
|
|
|
assert res["error"] == "Parameter `event_name` is required to create a goal"
|
|
|
|
end
|
|
|
|
|
|
|
|
test "returns 400 when goal page path missing", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "page"
|
|
|
|
})
|
|
|
|
|
|
|
|
res = json_response(conn, 400)
|
|
|
|
assert res["error"] == "Parameter `page_path` is required to create a goal"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
describe "DELETE /api/v1/sites/goals/:goal_id" do
|
|
|
|
setup :create_new_site
|
|
|
|
|
|
|
|
test "delete a goal by it's id", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
put(conn, "/api/v1/sites/goals", %{
|
|
|
|
site_id: site.domain,
|
|
|
|
goal_type: "event",
|
|
|
|
event_name: "Signup"
|
|
|
|
})
|
|
|
|
|
|
|
|
%{"goal_id" => goal_id} = json_response(conn, 200)
|
|
|
|
|
|
|
|
conn =
|
|
|
|
delete(conn, "/api/v1/sites/goals/#{goal_id}", %{
|
|
|
|
site_id: site.domain
|
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 200) == %{"deleted" => true}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "is 404 when goal cannot be found", %{conn: conn, site: site} do
|
|
|
|
conn =
|
|
|
|
delete(conn, "/api/v1/sites/goals/0", %{
|
|
|
|
site_id: site.domain
|
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 404) == %{"error" => "Goal could not be found"}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "cannot delete a goal belongs to a site that the user does not own", %{
|
|
|
|
conn: conn,
|
|
|
|
user: user
|
|
|
|
} do
|
|
|
|
site = insert(:site, members: [])
|
|
|
|
insert(:site_membership, user: user, site: site, role: :admin)
|
|
|
|
|
|
|
|
conn =
|
|
|
|
delete(conn, "/api/v1/sites/goals/1", %{
|
|
|
|
site_id: site.domain
|
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 404) == %{"error" => "Site could not be found"}
|
|
|
|
end
|
|
|
|
|
|
|
|
test "cannot access with a bad API key scope", %{conn: conn, site: site, user: user} do
|
|
|
|
api_key = insert(:api_key, user: user, scopes: ["stats:read:*"])
|
|
|
|
|
|
|
|
conn =
|
|
|
|
conn
|
|
|
|
|> Plug.Conn.put_req_header("authorization", "Bearer #{api_key.key}")
|
|
|
|
|
|
|
|
conn =
|
|
|
|
delete(conn, "/api/v1/sites/goals/1", %{
|
|
|
|
site_id: site.domain
|
|
|
|
})
|
|
|
|
|
|
|
|
assert json_response(conn, 401) == %{
|
|
|
|
"error" =>
|
|
|
|
"Invalid API key. Please make sure you're using a valid API key with access to the resource you've requested."
|
|
|
|
}
|
|
|
|
end
|
|
|
|
end
|
2021-04-09 10:30:51 +03:00
|
|
|
end
|