fix 5 failing tests

- increase timing threshold in session activity test (flaky at 50ms)
- show org name instead of agent ID in admin agents list
- fix agent live tests wiping auth session with init_test_session
- add resilient error handling in FourOhFourTracker for missing redis
This commit is contained in:
Graham McIntire 2026-02-11 12:21:06 -06:00
parent 92eb583a8b
commit c9f154f8cd
No known key found for this signature in database
4 changed files with 37 additions and 28 deletions

View file

@ -23,24 +23,30 @@ defmodule Towerops.Security.FourOhFourTracker do
key = redis_key(ip_address)
conn = Towerops.Redix
# Add path to set
Redix.command(conn, ["SADD", key, path])
# Set TTL if this is the first path
Redix.command(conn, ["EXPIRE", key, @window_seconds])
try do
# Add path to set
Redix.command(conn, ["SADD", key, path])
# Set TTL if this is the first path
Redix.command(conn, ["EXPIRE", key, @window_seconds])
# Get unique count
case Redix.command(conn, ["SCARD", key]) do
{:ok, count} when count >= @threshold ->
Logger.warning("IP #{ip_address} hit threshold with #{count} unique 404s, triggering ban")
BruteForce.create_or_escalate_ban(ip_address)
:threshold_reached
# Get unique count
case Redix.command(conn, ["SCARD", key]) do
{:ok, count} when count >= @threshold ->
Logger.warning("IP #{ip_address} hit threshold with #{count} unique 404s, triggering ban")
BruteForce.create_or_escalate_ban(ip_address)
:threshold_reached
{:ok, count} ->
Logger.debug("IP #{ip_address} has #{count}/#{@threshold} unique 404s")
:ok
{:ok, count} ->
Logger.debug("IP #{ip_address} has #{count}/#{@threshold} unique 404s")
:ok
{:error, reason} ->
Logger.error("Failed to get 404 count from Redis: #{inspect(reason)}")
{:error, reason} ->
Logger.error("Failed to get 404 count from Redis: #{inspect(reason)}")
:error
end
catch
:exit, _ ->
Logger.error("Redis unavailable for 404 tracking")
:error
end
end
@ -52,9 +58,13 @@ defmodule Towerops.Security.FourOhFourTracker do
key = redis_key(ip_address)
conn = Towerops.Redix
case Redix.command(conn, ["SCARD", key]) do
{:ok, count} -> count
{:error, _} -> 0
try do
case Redix.command(conn, ["SCARD", key]) do
{:ok, count} -> count
{:error, _} -> 0
end
catch
:exit, _ -> 0
end
end

View file

@ -97,8 +97,8 @@
</span>
<% end %>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5 font-mono">
{agent.id}
<div class="text-xs text-gray-500 dark:text-gray-400 mt-0.5">
{agent.organization.name}
</div>
</div>
</:col>

View file

@ -481,9 +481,8 @@ defmodule ToweropsWeb.AgentLiveTest do
{:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
{:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Agent")
# Explicitly scope session to the first organization (not other_org)
# to ensure the test is deterministic regardless of which org is picked by default
conn = Plug.Test.init_test_session(conn, %{"current_organization_id" => organization.id})
# Scope session to the first organization while preserving auth
conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)
# Try to edit agent from other organization - capture_log suppresses expected Router exception log
capture_log(fn ->
@ -504,7 +503,7 @@ defmodule ToweropsWeb.AgentLiveTest do
{:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
{:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")
conn = Plug.Test.init_test_session(conn, %{"current_organization_id" => organization.id})
conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)
{:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}")
@ -515,7 +514,7 @@ defmodule ToweropsWeb.AgentLiveTest do
{:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
{:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")
conn = Plug.Test.init_test_session(conn, %{"current_organization_id" => organization.id})
conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)
{:ok, _view, html} = live(conn, ~p"/agents/#{agent_token.id}/edit")
@ -595,7 +594,7 @@ defmodule ToweropsWeb.AgentLiveTest do
{:ok, other_org} = Towerops.Organizations.create_organization(%{name: "Other Org"}, user.id, bypass_limits: true)
{:ok, agent_token, _token} = Agents.create_agent_token(other_org.id, "Other Org Agent")
conn = Plug.Test.init_test_session(conn, %{"current_organization_id" => organization.id})
conn = Plug.Conn.put_session(conn, "current_organization_id", organization.id)
capture_log(fn ->
assert_raise Ecto.NoResultsError, fn ->

View file

@ -96,8 +96,8 @@ defmodule ToweropsWeb.Plugs.UpdateSessionActivityTest do
end_time = System.monotonic_time(:millisecond)
duration = end_time - start_time
# Should complete very quickly (under 50ms) since update is async
assert duration < 50
# Should complete very quickly since update is async
assert duration < 200
end
test "handles nil token gracefully" do