defmodule DiogramosWeb.CanvasChannelTest do
use DiogramosWeb.ChannelCase, async: true
import Diogramos.AccountsFixtures
import Diogramos.DiagramsFixtures
import Diogramos.DataCase, only: [allow_authority: 1]
alias Diogramos.Accounts
alias Diogramos.Accounts.Scope
alias Diogramos.Diagrams
alias DiogramosWeb.UserSocket
@rect %{
"type" => "insert_element",
"element" => %{"id" => "e1", "type" => "rect", "x" => 0, "y" => 0, "w" => 10, "h" => 10}
}
defp connect_as(user) do
token = Accounts.create_api_token(user)
{:ok, socket} =
Phoenix.ChannelTest.connect(UserSocket, %{"token" => token}, connect_info: %{})
socket
end
setup do
user = user_fixture()
scope = Scope.for_user(user)
canvas = canvas_fixture(scope)
allow_authority(canvas.id)
%{user: user, scope: scope, canvas: canvas}
end
describe "socket connect" do
test "accepts a valid api token", %{user: user} do
token = Accounts.create_api_token(user)
assert {:ok, socket} = connect(UserSocket, %{"token" => token}, connect_info: %{})
assert socket.assigns.user_id == user.id
assert UserSocket.id(socket) == "user_socket:#{user.id}"
end
test "rejects a bogus or missing token" do
assert :error = connect(UserSocket, %{"token" => "nope"}, connect_info: %{})
assert :error = connect(UserSocket, %{}, connect_info: %{})
end
test "rejects a revoked token", %{user: user} do
token = Accounts.create_api_token(user)
{_user, token_id} = Accounts.fetch_user_by_api_token(token)
:ok = Accounts.delete_api_token(user, token_id)
assert :error = connect(UserSocket, %{"token" => token}, connect_info: %{})
end
end
describe "join" do
test "replies with the snapshot", %{user: user, canvas: canvas} do
socket = connect_as(user)
assert {:ok, reply, _socket} = subscribe_and_join(socket, "canvas:#{canvas.id}", %{})
assert reply.version == 0
assert reply.role == "owner"
assert reply.document == %{"elements" => %{}, "order" => [], "connectors" => %{}}
assert reply.canvas.slug == canvas.slug
assert is_binary(reply.self_key)
end
test "a viewer joins and sees their role", %{canvas: canvas} do
viewer = user_fixture()
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", viewer.id, "viewer")
assert {:ok, reply, _socket} =
subscribe_and_join(connect_as(viewer), "canvas:#{canvas.id}", %{})
assert reply.role == "viewer"
end
test "refuses a canvas the user cannot read", %{canvas: canvas} do
stranger = user_fixture()
assert {:error, %{code: "not_found"}} =
subscribe_and_join(connect_as(stranger), "canvas:#{canvas.id}", %{})
end
test "refuses a missing canvas and a malformed topic", %{user: user} do
socket = connect_as(user)
assert {:error, %{code: "not_found"}} = subscribe_and_join(socket, "canvas:999999", %{})
assert {:error, %{code: "not_found"}} = subscribe_and_join(socket, "canvas:abc", %{})
end
end
describe "ops" do
setup %{user: user, canvas: canvas} do
{:ok, _reply, socket} = subscribe_and_join(connect_as(user), "canvas:#{canvas.id}", %{})
%{socket: socket}
end
test "applies an op and replies with the new version", %{socket: socket} do
ref = push(socket, "op", %{"op" => @rect, "actor_ref" => "abc"})
assert_reply ref, :ok, %{version: 1}
end
test "echoes the op back with the actor_ref so the sender can drop it", %{socket: socket} do
push(socket, "op", %{"op" => @rect, "actor_ref" => "abc"})
assert_push "op", %{op: op, version: 1, actor_ref: "abc"}
assert op["type"] == "insert_element"
end
test "pushes a peer's op to everyone joined", %{canvas: canvas, socket: socket} do
editor = user_fixture()
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", editor.id, "editor")
{:ok, _reply, other} = subscribe_and_join(connect_as(editor), "canvas:#{canvas.id}", %{})
ref = push(other, "op", %{"op" => @rect, "actor_ref" => "theirs"})
assert_reply ref, :ok, %{version: 1}
# Arrives on the first socket, tagged with the *other* actor_ref.
assert_push "op", %{version: 1, actor_ref: "theirs"}
refute socket == other
end
test "rejects an invalid op without advancing the version", %{socket: socket} do
bad = %{"type" => "insert_element", "element" => %{"id" => "x", "type" => "rect"}}
ref = push(socket, "op", %{"op" => bad, "actor_ref" => "a"})
assert_reply ref, :error, %{code: "invalid_element_geometry"}
ref = push(socket, "op", %{"op" => @rect, "actor_ref" => "b"})
assert_reply ref, :ok, %{version: 1}
end
test "rejects a malformed payload", %{socket: socket} do
ref = push(socket, "op", %{"actor_ref" => "a"})
assert_reply ref, :error, %{code: "invalid_op"}
end
test "a viewer's op is refused", %{canvas: canvas} do
viewer = user_fixture()
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", viewer.id, "viewer")
{:ok, _reply, socket} = subscribe_and_join(connect_as(viewer), "canvas:#{canvas.id}", %{})
ref = push(socket, "op", %{"op" => @rect, "actor_ref" => "a"})
assert_reply ref, :error, %{code: "forbidden"}
end
test "versions advance monotonically across ops", %{socket: socket} do
for {id, expected} <- Enum.with_index(~w(a b c), 1) do
element = %{"id" => id, "type" => "circle", "cx" => 1, "cy" => 1, "r" => 5}
ref = push(socket, "op", %{"op" => %{"type" => "insert_element", "element" => element}})
assert_reply ref, :ok, %{version: ^expected}
end
end
end
describe "presence" do
test "a joining peer is announced to the others", %{user: user, canvas: canvas} do
{:ok, _reply, _socket} = subscribe_and_join(connect_as(user), "canvas:#{canvas.id}", %{})
# Own presence is filtered out of the peer list.
assert_push "peers", %{peers: []}
editor = user_fixture()
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", editor.id, "editor")
{:ok, _reply, _other} = subscribe_and_join(connect_as(editor), "canvas:#{canvas.id}", %{})
assert_push "peers", %{peers: [%{source: "android"}]}, 1000
end
test "a cursor update reaches the other peer", %{user: user, canvas: canvas} do
{:ok, _reply, socket} = subscribe_and_join(connect_as(user), "canvas:#{canvas.id}", %{})
editor = user_fixture()
{:ok, _} = Diagrams.grant_permission("canvas", canvas.id, "user", editor.id, "editor")
{:ok, _reply, other} = subscribe_and_join(connect_as(editor), "canvas:#{canvas.id}", %{})
push(other, "cursor", %{"x" => 12, "y" => 34, "lock" => "e1"})
assert_push "peers", %{peers: [%{cursor: %{x: 12, y: 34}, lock: "e1"}]}, 1000
refute socket == other
end
end
end
neiam
/diogramos
diogramos
public · Issues · Pulls · Labels · Forks · Compare · Actions success · Badges · Packages
⭐
Log in to mark this repository.
6.8 KiB · text
History
dc2edab