test/openagents_web/live/computers_live_test.exs

58e6347eeb72 · 8 KB

defmodule OpenAgentsWeb.ComputersLiveTest do
  use OpenAgentsWeb.ConnCase, async: false
  import Phoenix.LiveViewTest

  alias OpenAgents.Computer
  alias OpenAgents.Machines

  test "shows a pairing path and an honest empty state", %{conn: conn} do
    conn = log_in_github_user(conn, "computers-empty")
    {:ok, view, _html} = live(conn, ~p"/computers")

    assert has_element?(view, "#pairing-card")
    assert has_element?(view, "#pairing-form")
    assert has_element?(view, "#computers-empty.hidden.only\\:block")
    assert has_element?(view, "#computers-list-heading")
    refute has_element?(view, "#computers-list .computer-card")
  end

  test "pairs a computer, resets the form, and updates the stream", %{conn: conn} do
    user = github_user("computers-pair")
    conn = Plug.Test.init_test_session(conn, %{"user_id" => user.id})
    pairing = start_pairing!("pairing-laptop")
    {:ok, view, _html} = live(conn, ~p"/computers")

    view
    |> form("#pairing-form", pairing: %{code: pairing.code})
    |> render_submit()

    assert has_element?(view, "#pairing-success", "pairing-laptop")
    assert has_element?(view, "#computer-#{paired_machine!(user).id}[data-presence=offline]")
    assert has_element?(view, "#computers-empty.hidden.only\\:block")
    assert has_element?(view, "#pairing_code[value='']")
  end

  test "renders typed pairing failures without replacing the form", %{conn: conn} do
    conn = log_in_github_user(conn, "computers-pair-error")
    {:ok, view, _html} = live(conn, ~p"/computers")

    view
    |> form("#pairing-form", pairing: %{code: "NOPE-1234"})
    |> render_submit()

    assert has_element?(view, "#pairing-error", "No pairing found")
    assert has_element?(view, "#pairing-form")
    refute has_element?(view, "#pairing-success")
  end

  test "presence and last-seen updates arrive without a page refresh", %{conn: conn} do
    user = github_user("computers-presence")
    machine = pair!(user, "presence-box")
    conn = Plug.Test.init_test_session(conn, %{"user_id" => user.id})
    {:ok, view, _html} = live(conn, ~p"/computers")

    assert has_element?(
             view,
             "#computer-#{machine.id}[data-state=active][data-presence=offline]",
             "Offline"
           )

    assert {:ok, _owner} = Computer.register(machine.id)
    _state = :sys.get_state(view.pid)
    assert has_element?(view, "#computer-#{machine.id}[data-presence=online]", "Online")

    assert {:ok, _updated} =
             Machines.store_probe(machine, %{
               "platform" => "linux",
               "acp_agents" => [
                 %{
                   "id" => "codex",
                   "model" => "gpt-5.6-sol",
                   "reasoning_effort" => "medium",
                   "mode" => "agent-full-access"
                 }
               ]
             })

    _state = :sys.get_state(view.pid)
    assert has_element?(view, "#computer-#{machine.id} time[datetime]")

    assert has_element?(
             view,
             "#computer-agent-#{machine.id}",
             "gpt-5.6-sol · medium · agent-full-access"
           )

    assert :ok = Computer.unregister(machine.id)
    _state = :sys.get_state(view.pid)
    assert has_element?(view, "#computer-#{machine.id}[data-presence=offline]", "Offline")
  end

  test "revocation is confirmed, disconnects access, and announces its own outcome", %{conn: conn} do
    user = github_user("computers-revoke")
    machine = pair!(user, "revoked-box")
    conn = Plug.Test.init_test_session(conn, %{"user_id" => user.id})
    {:ok, view, _html} = live(conn, ~p"/computers")

    assert has_element?(
             view,
             "#revoke-#{machine.id}[data-confirm][phx-disable-with='Revoking…']",
             "Revoke access"
           )

    view |> element("#revoke-#{machine.id}") |> render_click()

    assert has_element?(view, "#revocation-success", "revoked-box")
    assert has_element?(view, "#revocation-success [data-title]", "REVOKED")
    assert has_element?(view, "#computer-#{machine.id}[data-state=revoked]")
    refute has_element?(view, "#revoke-#{machine.id}")
  end

  test "owners can toggle scoped forge credentials on an existing computer", %{conn: conn} do
    user = github_user("computers-credentials-policy")
    machine = pair!(user, "credentials-policy")
    conn = Plug.Test.init_test_session(conn, %{"user_id" => user.id})
    {:ok, view, _html} = live(conn, ~p"/computers")

    assert has_element?(
             view,
             "#credentials-policy-#{machine.id}",
             "Allow scoped forge credentials"
           )

    view |> element("#credentials-policy-#{machine.id}") |> render_click()

    assert has_element?(view, "#credentials-policy-success", "allowed")

    assert {:ok, %{scoped_forge_credentials_enabled: true}} =
             Machines.get_machine(user.id, machine.id)

    view |> element("#credentials-policy-#{machine.id}") |> render_click()

    assert has_element?(view, "#credentials-policy-success", "disabled")
    assert {:ok, updated} = Machines.get_machine(user.id, machine.id)
    refute updated.scoped_forge_credentials_enabled
  end

  test "feature-disabled environments keep management honest", %{conn: conn} do
    previous = Application.fetch_env!(:openagents, :computer_controller_enabled)
    Application.put_env(:openagents, :computer_controller_enabled, false)
    on_exit(fn -> Application.put_env(:openagents, :computer_controller_enabled, previous) end)

    user = github_user("computers-disabled")
    machine = pair!(user, "existing-box")
    conn = Plug.Test.init_test_session(conn, %{"user_id" => user.id})
    {:ok, view, _html} = live(conn, ~p"/computers")

    assert has_element?(view, "#computer-controller-disabled", "New pairing codes are disabled")
    refute has_element?(view, "#pairing-form")
    assert has_element?(view, "#computer-#{machine.id}")
    assert has_element?(view, "#revoke-#{machine.id}")
  end

  defp paired_machine!(user) do
    [machine] = Machines.list_machines(user.id)
    machine
  end

  defp pair!(user, name) do
    pairing = start_pairing!(name)
    assert {:ok, machine} = Machines.approve_pairing(user, pairing.code)
    machine
  end

  defp start_pairing!(name) do
    assert {:ok, pairing} =
             Machines.start_pairing(%{
               "name" => name,
               "tier" => "curated",
               "platform" => "linux-x64",
               "agent_version" => "0.4.0",
               "roots" => ["/home/test/work"]
             })

    pairing
  end

  describe "the pairing form is usable" do
    # Three attempts to pair expired unapproved during one working session,
    # partly because this form was hard to fill: `computers-pairing__form` was a
    # two-column grid with three children, so the checkbox landed beside the
    # code label and the button wrapped underneath into the same cell — the
    # words "Approve pairing" rendered through the checkbox text. See #112.
    test "names the command that actually produces a code", %{conn: conn} do
      conn = log_in_github_user(conn, "computers-command")
      {:ok, _view, html} = live(conn, ~p"/computers")

      assert html =~ "oa computer pair"

      refute html =~ "sarah-computer-controller",
             "the page tells the reader to run a command that does not exist"
    end

    test "prefills a code carried in the link, the way /device does", %{conn: conn} do
      conn = log_in_github_user(conn, "computers-prefill")
      {:ok, view, _html} = live(conn, ~p"/computers?user_code=M6YF-BP8Q")

      assert has_element?(view, ~s(#pairing-form input[value="M6YF-BP8Q"])),
             "the code in the link did not reach the field, so it has to be retyped"
    end

    test "lower case in the link still fills the field", %{conn: conn} do
      conn = log_in_github_user(conn, "computers-prefill-case")
      {:ok, view, _html} = live(conn, ~p"/computers?user_code=m6yf-bp8q")

      assert has_element?(view, ~s(#pairing-form input[value="M6YF-BP8Q"]))
    end

    test "only a code-shaped value is prefilled", %{conn: conn} do
      conn = log_in_github_user(conn, "computers-prefill-junk")
      {:ok, view, _html} = live(conn, ~p"/computers?user_code=<script>alert(1)</script>")

      # A crafted link must not put arbitrary text in front of the reader as
      # though the server had issued it.
      assert has_element?(view, ~s(#pairing-form input[value=""]))
    end

    test "no link, no prefill", %{conn: conn} do
      conn = log_in_github_user(conn, "computers-prefill-none")
      {:ok, view, _html} = live(conn, ~p"/computers")

      assert has_element?(view, ~s(#pairing-form input[value=""]))
    end
  end
end