4.1 KiB · text History dc2edab
defmodule DiogramosWeb.UserLive.ConnectedDevicesTest do
  use DiogramosWeb.ConnCase, async: false

  import Phoenix.LiveViewTest
  import Diogramos.AccountsFixtures

  alias Diogramos.Accounts

  setup %{conn: conn} do
    on_exit(fn -> System.delete_env("DIOGRAMOS_PUBLIC_URL") end)

    user = user_fixture()
    %{conn: log_in_user(conn, user), user: user}
  end

  describe "the panel" do
    test "shows an empty state before any device is paired", %{conn: conn} do
      {:ok, _lv, html} = live(conn, ~p"/users/settings")

      assert html =~ "Connected devices"
      assert html =~ "No tokens yet"
    end

    test "lists existing tokens with their labels", %{conn: conn, user: user} do
      Accounts.create_api_token(user, %{label: "Pixel 7"})

      {:ok, _lv, html} = live(conn, ~p"/users/settings")

      assert html =~ "Pixel 7"
      refute html =~ "No tokens yet"
    end

    test "labels an unlabelled token rather than rendering a blank row", %{
      conn: conn,
      user: user
    } do
      Accounts.create_api_token(user)

      {:ok, _lv, html} = live(conn, ~p"/users/settings")
      assert html =~ "(no label)"
    end

    test "does not list another user's tokens", %{conn: conn} do
      Accounts.create_api_token(user_fixture(), %{label: "Someone else's phone"})

      {:ok, _lv, html} = live(conn, ~p"/users/settings")

      refute html =~ "Someone else's phone"
      assert html =~ "No tokens yet"
    end
  end

  describe "generating a token" do
    test "renders a working token, a QR, and the pairing URI", %{conn: conn, user: user} do
      System.put_env("DIOGRAMOS_PUBLIC_URL", "https://diagrams.example.com")

      {:ok, lv, _html} = live(conn, ~p"/users/settings")

      html =
        lv
        |> form("#token_form", token: %{label: "Pixel 7"})
        |> render_submit()

      assert html =~ "diogramos://pair?"
      assert html =~ "https%3A%2F%2Fdiagrams.example.com"
      assert html =~ "<svg"
      assert html =~ "only time the token is shown"

      # The token on screen is the real bearer, not a display stand-in.
      [_, token] = Regex.run(~r/token=([A-Za-z0-9_-]+)/, html)
      assert {fetched, _id} = Accounts.fetch_user_by_api_token(token)
      assert fetched.id == user.id

      assert [%{label: "Pixel 7"}] = Accounts.list_api_tokens(user)
    end

    test "an empty label is stored as none", %{conn: conn, user: user} do
      {:ok, lv, _html} = live(conn, ~p"/users/settings")

      lv |> form("#token_form", token: %{label: ""}) |> render_submit()

      assert [%{label: nil}] = Accounts.list_api_tokens(user)
    end

    test "the panel can be dismissed, and the token is not shown again", %{conn: conn} do
      {:ok, lv, _html} = live(conn, ~p"/users/settings")

      html = lv |> form("#token_form", token: %{label: "Pixel"}) |> render_submit()
      assert html =~ "diogramos://pair?"

      html = lv |> element("button", "Done — hide") |> render_click()

      refute html =~ "diogramos://pair?"
      # ...but the device itself is still listed.
      assert html =~ "Pixel"
    end
  end

  describe "revoking" do
    test "removes the row and kills the token", %{conn: conn, user: user} do
      token = Accounts.create_api_token(user, %{label: "Old phone"})
      [%{id: id}] = Accounts.list_api_tokens(user)

      {:ok, lv, html} = live(conn, ~p"/users/settings")
      assert html =~ "Old phone"

      html = lv |> element("#api-token-#{id} button", "Revoke") |> render_click()

      refute html =~ "Old phone"
      assert html =~ "No tokens yet"
      refute Accounts.fetch_user_by_api_token(token)
    end

    test "cannot revoke a token belonging to someone else", %{conn: conn, user: user} do
      other = user_fixture()
      their_token = Accounts.create_api_token(other)
      [%{id: their_id}] = Accounts.list_api_tokens(other)

      {:ok, lv, _html} = live(conn, ~p"/users/settings")

      render_click(lv, "revoke_api_token", %{"id" => to_string(their_id)})

      # Still valid: the id belongs to another user, so the delete is scoped out.
      assert Accounts.fetch_user_by_api_token(their_token)
      assert Accounts.list_api_tokens(user) == []
    end
  end
end