test/openagents_web/live/chat_console_fleet_test.exs

58e6347eeb72 · 5 KB

defmodule OpenAgentsWeb.ChatConsoleFleetTest do
  use OpenAgentsWeb.ConnCase
  import Phoenix.LiveViewTest

  alias OpenAgents.Box.{ConversationBox, FanoutItem, FanoutRequest, Run}
  alias OpenAgents.Conversations
  alias OpenAgents.Machines
  alias OpenAgents.Repo

  test "renders the durable fleet projection and reconstructs it after reload", %{conn: conn} do
    key = "fleet-console-owner"
    user = github_user(key)
    {:ok, conversation} = Conversations.ensure_conversation(user)
    box = insert_box(conversation.id, "bx_console_fleet", "box-1")
    insert_run(conversation.id, box.id, "completed", "done https://viewer.ascii.dev/secret")
    insert_queue(conversation.id)

    conn = log_in_admin_user(conn, key)
    {:ok, view, _html} = live(conn, ~p"/chat")

    assert has_element?(view, "#chat-console-fleet")
    assert has_element?(view, "#chat-console-fleet-cap", "Admitted 1 of 2")
    assert has_element?(view, "#chat-console-fleet-box-#{box.id}", "box-1")
    assert has_element?(view, "#chat-console-fleet-run-state-#{box.id}", "completed")
    assert has_element?(view, "#chat-console-fleet-output-#{box.id}", "done")
    refute render(view) =~ "viewer.ascii.dev"
    assert has_element?(view, "#chat-console-fleet-queue", "admission_pending")
    assert has_element?(view, "#chat-console-fleet-queue article")

    {:ok, reloaded, _html} = live(conn, ~p"/chat")
    assert has_element?(reloaded, "#chat-console-fleet-box-#{box.id}", "box-1")
    assert has_element?(reloaded, "#chat-console-fleet-output-#{box.id}", "done")
    assert has_element?(reloaded, "#chat-console-fleet-queue", "admission_pending")
  end

  test "renders owner controls for an active box and run", %{conn: conn} do
    key = "fleet-console-controls"
    user = github_user(key)
    {:ok, conversation} = Conversations.ensure_conversation(user)
    box = insert_box(conversation.id, "bx_console_controls", "box-controls")
    run = insert_run(conversation.id, box.id, "running", "")

    conn = log_in_admin_user(conn, key)
    {:ok, view, _html} = live(conn, ~p"/chat")

    assert has_element?(view, "#chat-console-fleet-stop-#{box.id}")
    assert has_element?(view, "#chat-console-fleet-cancel-#{box.id}")
    assert render(view) =~ ~s(phx-value-box-id="box-controls")
    assert render(view) =~ ~s(phx-value-run-id="#{run.id}")
  end

  test "renders a safe failure when a control targets a missing box", %{conn: conn} do
    key = "fleet-console-refused-control"
    user = github_user(key)
    {:ok, _conversation} = Conversations.ensure_conversation(user)

    conn = log_in_admin_user(conn, key)
    {:ok, view, _html} = live(conn, ~p"/chat")
    refute has_element?(view, "#chat-console-fleet")

    html = render_click(view, "stop_box", %{"box-id" => "missing-box"})

    assert html =~ "That computer is no longer available."
  end

  test "renders paired Computers from the durable projection", %{conn: conn} do
    key = "fleet-console-computer"
    user = github_user(key)
    {:ok, _conversation} = Conversations.ensure_conversation(user)

    {:ok, pairing} =
      Machines.start_pairing(%{
        "name" => "local-coding-computer",
        "tier" => "curated",
        "platform" => "linux-x64",
        "agent_version" => "0.4.0",
        "roots" => ["/workspace"]
      })

    {:ok, machine} = Machines.approve_pairing(user, pairing.code)

    {:ok, _machine} =
      Machines.store_probe(machine, %{
        "acp_agents" => [%{"id" => "codex", "version" => "1.2.3"}]
      })

    conn = log_in_admin_user(conn, key)
    {:ok, view, _html} = live(conn, ~p"/chat")

    assert has_element?(view, "#chat-console-fleet")

    assert has_element?(
             view,
             "#chat-console-fleet-computer-#{machine.id}",
             "local-coding-computer"
           )

    assert has_element?(view, "#chat-console-fleet-computer-#{machine.id}", "curated")
    assert render(view) =~ "active"
    refute render(view) =~ "token_digest"
  end

  defp insert_box(conversation_id, box_id, label) do
    %ConversationBox{}
    |> ConversationBox.changeset(%{
      conversation_id: conversation_id,
      box_id: box_id,
      label: label,
      state: "ready",
      setup_status: "done"
    })
    |> Repo.insert!()
  end

  defp insert_run(conversation_id, box_id, state, output) do
    now = DateTime.utc_now() |> DateTime.truncate(:microsecond)

    %Run{}
    |> Run.changeset(%{
      conversation_id: conversation_id,
      conversation_box_id: box_id,
      requesting_principal: %{"type" => "user"},
      command: "echo console",
      idempotency_key: Ecto.UUID.generate(),
      state: state,
      output: output,
      run_directory: "$HOME/.openagents/box-runs/console/#{Ecto.UUID.generate()}",
      admitted_at: now,
      deadline_at: DateTime.add(now, 60, :second),
      finished_at: now,
      exit_status: 0
    })
    |> Repo.insert!()
  end

  defp insert_queue(conversation_id) do
    request =
      %FanoutRequest{}
      |> FanoutRequest.changeset(%{
        conversation_id: conversation_id,
        requesting_principal: %{"type" => "user"},
        requested_count: 1,
        effective_limits: %{"conversation_active_limit" => 2},
        queued_count: 1,
        state: "queued"
      })
      |> Repo.insert!()

    %FanoutItem{}
    |> FanoutItem.changeset(%{
      request_id: request.id,
      conversation_id: conversation_id,
      position: 0,
      label: "box-2",
      requesting_principal: %{"type" => "user"},
      state: "queued",
      queue_reason: "admission_pending",
      estimated_burn_rate_microusd: 100_000
    })
    |> Repo.insert!()
  end
end