towerops/test/towerops_web/live/agent_live_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

162 lines
5.2 KiB
Elixir

defmodule ToweropsWeb.AgentLiveTest do
use ToweropsWeb.ConnCase
import Ecto.Query
import Phoenix.LiveViewTest
alias Towerops.Agents
setup :register_and_log_in_user
setup %{user: user} do
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
%{organization: organization}
end
describe "Index" do
test "lists all agent tokens", %{conn: conn, organization: organization} do
{:ok, _agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "Remote Agents"
assert html =~ "Agent 1"
end
test "displays empty state when no agents", %{conn: conn, organization: organization} do
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "No agents"
assert html =~ "Get started by creating your first remote agent"
end
test "creates new agent and shows token modal", %{conn: conn, organization: organization} do
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
html =
view
|> form("#new-agent-form form", %{name: "Test Agent"})
|> render_submit()
assert html =~ "Agent Setup Instructions"
assert html =~ "Test Agent"
assert html =~ "Authentication Token"
end
test "deletes agent", %{conn: conn, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
html =
view
|> element("button", "Delete")
|> render_click()
assert html =~ "Agent deleted successfully"
# Verify agent was actually deleted
assert is_nil(Towerops.Repo.get(Agents.AgentToken, agent_token.id))
end
test "closes token modal", %{conn: conn, organization: organization} do
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
# Create agent to show modal
view
|> form("#new-agent-form form", %{name: "Test Agent"})
|> render_submit()
# Close modal
html =
view
|> element("button", "I've Saved the Token")
|> render_click()
refute html =~ "Agent Created Successfully"
end
test "requires authentication", %{organization: organization} do
conn = build_conn()
{:error, redirect} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert {:redirect, %{to: path}} = redirect
assert path == ~p"/users/log-in"
end
test "displays agent with warning status", %{conn: conn, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
# Update last_seen_at to 3 minutes ago (warning status)
three_minutes_ago = DateTime.add(DateTime.utc_now(), -180, :second)
Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")
Towerops.Repo.update_all(
from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
set: [last_seen_at: three_minutes_ago]
)
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "Warning"
assert html =~ "3m ago"
end
test "displays agent with offline status", %{conn: conn, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
# Update last_seen_at to 10 minutes ago (offline status)
ten_minutes_ago = DateTime.add(DateTime.utc_now(), -600, :second)
Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")
Towerops.Repo.update_all(
from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
set: [last_seen_at: ten_minutes_ago]
)
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "Offline"
assert html =~ "10m ago"
end
test "displays agent last seen time in hours", %{conn: conn, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
# Update last_seen_at to 2 hours ago
two_hours_ago = DateTime.add(DateTime.utc_now(), -7200, :second)
Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")
Towerops.Repo.update_all(
from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
set: [last_seen_at: two_hours_ago]
)
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "2h ago"
end
test "displays agent last seen time in days", %{conn: conn, organization: organization} do
{:ok, agent_token, _token} = Agents.create_agent_token(organization.id, "Agent 1")
# Update last_seen_at to 2 days ago
two_days_ago = DateTime.add(DateTime.utc_now(), -172_800, :second)
Agents.update_agent_token_heartbeat(agent_token.id, "127.0.0.1")
Towerops.Repo.update_all(
from(t in Agents.AgentToken, where: t.id == ^agent_token.id),
set: [last_seen_at: two_days_ago]
)
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}/agents")
assert html =~ "2d ago"
end
end
end