defmodule TarakanWeb.ClientAuthorizationLiveTest do
  use TarakanWeb.ConnCase, async: true

  import Phoenix.LiveViewTest

  alias Tarakan.Accounts.ClientAuthorizations

  setup :register_and_log_in_account

  test "approve/deny with no loaded authorization flashes instead of crashing", %{conn: conn} do
    # An unknown/expired user code leaves @authorization nil and hides the
    # buttons - but a queued or crafted event must not reach the context with
    # nil and raise a FunctionClauseError.
    {:ok, view, html} = live(conn, ~p"/client/authorize/UNKNOWNXY")
    assert html =~ "invalid or expired"

    assert render_click(view, "approve") =~ "expired or was already used"
    assert render_click(view, "deny") =~ "expired or was already used"
  end

  describe "consent screen" do
    test "attributes the client-supplied name instead of asserting it", %{conn: conn} do
      {:ok, _device_code, user_code, _authorization} =
        ClientAuthorizations.start(%{"client_name" => "Totally Official Thing"})

      {:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")

      # The name is presented as the request's own claim, next to a warning,
      # rather than as a statement this page vouches for.
      assert html =~ "Calls itself"
      assert html =~ "Totally Official Thing"
      assert html =~ "Nobody at Tarakan will"
    end

    test "a name carrying control or bidi characters is cleaned before display", %{conn: conn} do
      hostile = "Tarakan" <> "\u202e" <> "lortnoC" <> "\u200b" <> " Client"

      {:ok, _device_code, user_code, authorization} =
        ClientAuthorizations.start(%{"client_name" => hostile})

      refute authorization.client_name =~ "\u202e"
      refute authorization.client_name =~ "\u200b"

      {:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")
      refute html =~ "\u202e"
    end

    test "a name of only unusable characters falls back to the default", %{conn: _conn} do
      {:ok, _device_code, _user_code, authorization} =
        ClientAuthorizations.start(%{"client_name" => "\u200b\u202e"})

      assert authorization.client_name == "Tarakan Client"
    end

    test "a name with markup-shaped characters is refused outright" do
      assert {:error, changeset} =
               ClientAuthorizations.start(%{"client_name" => "Tarakan <script> Client"})

      assert Keyword.has_key?(changeset.errors, :client_name)
    end
  end

  describe "user code lookups" do
    test "wrong guesses are budgeted per account", %{conn: conn} do
      # Well past the per-account allowance; the page must stay uninformative
      # rather than answering "exists / does not exist" indefinitely.
      for index <- 1..35 do
        {:ok, _view, html} = live(conn, ~p"/client/authorize/#{"AAAA000" <> to_string(index)}")
        assert html =~ "invalid or expired"
      end

      # A real code now reads the same as a wrong one: throttled and missing
      # are deliberately indistinguishable.
      {:ok, _device_code, user_code, _authorization} = ClientAuthorizations.start()
      {:ok, _view, html} = live(conn, ~p"/client/authorize/#{user_code}")
      assert html =~ "invalid or expired"
    end
  end
end
