towerops/test/towerops_web/live/agent_live/index_test.exs
Graham McIntire c35a502425
Change agent revoke to delete with full cleanup
Replace the revoke functionality with delete to properly clean up agent references.
When an agent is deleted:
- All direct device assignments are removed
- Agent is removed from site defaults
- Agent is removed from organization defaults
- Devices automatically fall back to site/org agents or cloud polling

This ensures no orphaned references and provides better clarity about
what happens to device monitoring when an agent is removed.

- Add delete_agent_token/1 function in Agents context
- Update LiveView to use delete_agent event instead of revoke_agent
- Update UI button from "Revoke" to "Delete" with clearer confirmation message
- Add comprehensive tests for delete functionality and fallback behavior
- All 792 tests passing, no Credo warnings
2026-01-17 15:40:09 -06:00

203 lines
6.1 KiB
Elixir

defmodule ToweropsWeb.AgentLive.IndexTest do
use ToweropsWeb.ConnCase, async: true
import Phoenix.LiveViewTest
import Towerops.AccountsFixtures
alias Towerops.Agents
setup do
user = user_fixture()
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
%{user: user, organization: organization}
end
describe "mount" do
test "loads agent tokens for organization", %{conn: conn, user: user, organization: organization} do
# Create some agent tokens
{:ok, agent_token1, _token1} = Agents.create_agent_token(organization.id, "Agent 1")
{:ok, agent_token2, _token2} = Agents.create_agent_token(organization.id, "Agent 2")
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, _view, html} = live(conn)
assert html =~ "Remote Agents"
assert html =~ agent_token1.name
assert html =~ agent_token2.name
end
test "shows empty state when no agents exist", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, _view, html} = live(conn)
assert html =~ "Remote Agents"
end
test "requires authentication", %{conn: conn, organization: organization} do
conn = get(conn, "/orgs/#{organization.slug}/agents")
assert redirected_to(conn) == ~p"/users/log-in"
end
end
describe "create_agent event" do
test "creates new agent token and shows modal", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
# Trigger create_agent event with nested params
html = render_submit(view, "create_agent", %{"agent_form" => %{"name" => "New Agent"}})
assert html =~ "Agent created successfully"
assert html =~ "New Agent"
# Verify agent was actually created
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
assert length(agent_tokens) == 1
assert hd(agent_tokens).name == "New Agent"
end
test "creates agent with flat params format", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
# Trigger create_agent event with flat params
html = render_submit(view, "create_agent", %{"name" => "Flat Agent"})
assert html =~ "Agent created successfully"
assert html =~ "Flat Agent"
end
test "shows error on creation failure", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
# Trigger create_agent with invalid params (empty name)
html = render_submit(view, "create_agent", %{"agent_form" => %{"name" => ""}})
assert html =~ "Failed to create agent"
end
end
describe "close_token_modal event" do
test "closes the token modal", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
# First create an agent to show the modal
render_submit(view, "create_agent", %{"name" => "Test Agent"})
# Now close the modal
_html = render_click(view, "close_token_modal", %{})
# Modal should be closed - verify by checking that we can still interact with the view
assert render(view) =~ "Test Agent"
end
end
describe "delete_agent event" do
test "deletes agent token successfully", %{conn: conn, user: user, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent to Delete")
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
html = render_click(view, "delete_agent", %{"id" => agent_token.id})
assert html =~ "Agent deleted successfully"
# Verify agent was deleted (no longer in list)
agent_tokens = Agents.list_organization_agent_tokens(organization.id)
assert Enum.empty?(agent_tokens)
end
end
describe "agent status display" do
test "shows Never connected for agents without last_seen_at", %{
conn: conn,
user: user,
organization: organization
} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Never Seen")
# Verify the agent has no last_seen_at
assert is_nil(agent_token.last_seen_at)
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, _view, html} = live(conn)
assert html =~ "Never Seen"
assert html =~ "Never connected" or html =~ "Never"
end
test "shows agent status information in the page", %{
conn: conn,
user: user,
organization: organization
} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Status Agent")
# Update the agent to have a recent last_seen_at
agent_token
|> Ecto.Changeset.change(%{last_seen_at: DateTime.truncate(DateTime.utc_now(), :second)})
|> Towerops.Repo.update!()
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, _view, html} = live(conn)
assert html =~ "Status Agent"
# The status should show something about timing (Online, seconds ago, etc.)
assert html =~ "ago" or html =~ "Online"
end
end
describe "handle_params" do
test "handles :index action", %{conn: conn, user: user, organization: organization} do
conn =
conn
|> log_in_user(user)
|> get("/orgs/#{organization.slug}/agents")
{:ok, view, _html} = live(conn)
# Verify we can navigate to the index action
assert render(view) =~ "Remote Agents"
end
end
end