defmodule DiogramosWeb.Api.V1.CanvasApiTest do
use DiogramosWeb.ConnCase, async: true
import Diogramos.AccountsFixtures
import Diogramos.DiagramsFixtures
# Reads and ops go through the per-canvas Authority GenServer, which is
# a separate process and so needs its own sandbox allowance.
import Diogramos.DataCase, only: [allow_authority: 1]
alias Diogramos.Accounts
alias Diogramos.Accounts.Scope
alias Diogramos.Diagrams
setup %{conn: conn} do
user = user_fixture()
scope = Scope.for_user(user)
token = Accounts.create_api_token(user)
conn =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("authorization", "Bearer #{token}")
%{conn: conn, user: user, scope: scope, token: token}
end
describe "authentication" do
test "rejects a request with no token", %{conn: conn} do
conn = conn |> delete_req_header("authorization") |> get(~p"/api/v1/users/me")
assert %{"error" => %{"code" => "unauthenticated"}} = json_response(conn, 401)
end
test "rejects a malformed header and a bogus token", %{conn: conn} do
for header <- ["Bearer", "Basic abc", "Bearer nope"] do
conn =
conn
|> recycle()
|> put_req_header("accept", "application/json")
|> put_req_header("authorization", header)
|> get(~p"/api/v1/users/me")
assert json_response(conn, 401)
end
end
test "a revoked token stops working", %{conn: conn, user: user, token: token} do
{_user, token_id} = Accounts.fetch_user_by_api_token(token)
:ok = Accounts.delete_api_token(user, token_id)
assert json_response(get(conn, ~p"/api/v1/users/me"), 401)
end
test "GET /users/me returns the identity", %{conn: conn, user: user} do
assert %{"id" => id, "email" => email} = json_response(get(conn, ~p"/api/v1/users/me"), 200)
assert id == user.id
assert email == user.email
end
end
describe "GET /canvases" do
test "lists own canvases without documents", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
assert %{"data" => [entry]} = json_response(get(conn, ~p"/api/v1/canvases"), 200)
assert entry["slug"] == canvas.slug
assert entry["role"] == "owner"
refute Map.has_key?(entry, "document")
end
test "does not list other people's canvases", %{conn: conn} do
canvas_fixture(Scope.for_user(user_fixture()))
assert %{"data" => []} = json_response(get(conn, ~p"/api/v1/canvases"), 200)
end
test "filters by folder", %{conn: conn, scope: scope} do
folder = folder_fixture(scope)
in_folder = canvas_fixture(scope, %{folder_id: folder.id})
_loose = canvas_fixture(scope)
conn = get(conn, ~p"/api/v1/canvases?folder_id=#{folder.id}")
assert %{"data" => [entry]} = json_response(conn, 200)
assert entry["slug"] == in_folder.slug
end
end
describe "GET /canvases/:slug" do
test "returns the document verbatim", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
allow_authority(canvas.id)
assert %{"data" => data} =
json_response(get(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 200)
assert data["document"] == %{"elements" => %{}, "order" => [], "connectors" => %{}}
assert data["version"] == 0
assert data["role"] == "owner"
end
test "reads through the authority, not the stale row", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
allow_authority(canvas.id)
op = %{
"type" => "insert_element",
"element" => %{"id" => "e1", "type" => "rect", "x" => 0, "y" => 0, "w" => 10, "h" => 10}
}
{:ok, _} = Diagrams.Authority.apply_op(canvas.id, scope, op, "test")
# The canvases row still says version 0 — the authority snapshots
# only every 25 ops — so a DB read here would hand back an empty
# document.
assert %{"data" => data} =
json_response(get(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 200)
assert data["version"] == 1
assert %{"elements" => %{"e1" => _}} = data["document"]
end
test "404s for someone else's canvas rather than 403", %{conn: conn} do
canvas = canvas_fixture(Scope.for_user(user_fixture()))
assert %{"error" => %{"code" => "not_found"}} =
json_response(get(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 404)
end
end
describe "writes" do
test "creates a canvas", %{conn: conn} do
params = %{slug: "from-android", title: "From Android", theme: "forest"}
assert %{"data" => data} = json_response(post(conn, ~p"/api/v1/canvases", params), 201)
assert data["slug"] == "from-android"
assert data["theme"] == "forest"
end
test "reports validation errors with details", %{conn: conn} do
params = %{slug: "Not A Slug", title: ""}
assert %{"error" => %{"code" => "unprocessable", "details" => details}} =
json_response(post(conn, ~p"/api/v1/canvases", params), 422)
assert details["slug"]
assert details["title"]
end
test "updates metadata but ignores document and version", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
params = %{title: "Renamed", version: 99, document: %{"elements" => %{"x" => %{}}}}
assert %{"data" => data} =
json_response(patch(conn, ~p"/api/v1/canvases/#{canvas.slug}", params), 200)
assert data["title"] == "Renamed"
assert data["version"] == 0
assert data["document"] == %{"elements" => %{}, "order" => [], "connectors" => %{}}
end
test "a viewer may read but not write", %{conn: conn, user: user} do
owner = user_fixture()
canvas = canvas_fixture(Scope.for_user(owner))
allow_authority(canvas.id)
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", user.id, "viewer")
assert json_response(get(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 200)
conn = recycle_authed(conn)
assert %{"error" => %{"code" => "forbidden"}} =
json_response(
patch(conn, ~p"/api/v1/canvases/#{canvas.slug}", %{title: "Nope"}),
403
)
end
test "an editor may not delete", %{conn: conn, user: user} do
owner = user_fixture()
canvas = canvas_fixture(Scope.for_user(owner))
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", user.id, "editor")
assert %{"error" => %{"code" => "forbidden"}} =
json_response(delete(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 403)
end
test "the owner may delete", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
assert response(delete(conn, ~p"/api/v1/canvases/#{canvas.slug}"), 204)
refute Diagrams.get_canvas_by_slug(scope, canvas.slug)
end
end
describe "exports and metadata" do
test "exports json and mermaid", %{conn: conn, scope: scope} do
canvas = canvas_fixture(scope)
allow_authority(canvas.id)
assert %{"format" => _} =
json_response(get(conn, ~p"/api/v1/canvases/#{canvas.slug}/export.json"), 200)
assert response(
get(recycle_authed(conn), ~p"/api/v1/canvases/#{canvas.slug}/export.mermaid"),
200
)
end
test "serves the theme and token whitelists", %{conn: conn} do
assert %{"themes" => themes, "color_tokens" => tokens} =
json_response(get(conn, ~p"/api/v1/themes"), 200)
assert "afterdark" in themes
assert "base-100" in tokens
end
test "lists folders", %{conn: conn, scope: scope} do
folder = folder_fixture(scope)
assert %{"data" => [entry]} = json_response(get(conn, ~p"/api/v1/folders"), 200)
assert entry["id"] == folder.id
end
end
defp recycle_authed(conn) do
[auth] = Plug.Conn.get_req_header(conn, "authorization")
conn
|> recycle()
|> put_req_header("accept", "application/json")
|> put_req_header("authorization", auth)
end
end
neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
7.9 KiB · text
History
dc2edab