test fix and preseem check fix

This commit is contained in:
Graham McIntire 2026-02-13 09:42:39 -06:00
parent 53ccc27fa8
commit b3f8204047
No known key found for this signature in database
11 changed files with 89 additions and 59 deletions

View file

@ -90,12 +90,21 @@ defmodule Towerops.Preseem.Client do
end
defp request(method, url, api_key) do
Req.request(
opts = [
method: method,
url: url,
headers: [{"authorization", "Bearer #{api_key}"}, {"accept", "application/json"}],
plug: {Req.Test, __MODULE__}
)
headers: [{"authorization", "Bearer #{api_key}"}, {"accept", "application/json"}]
]
# Only use Req.Test plug in test environment
opts =
if Application.get_env(:towerops, :env) == :test do
Keyword.put(opts, :plug, {Req.Test, __MODULE__})
else
opts
end
Req.request(opts)
rescue
exception ->
{:error, Exception.message(exception)}

View file

@ -74,8 +74,9 @@ defmodule Towerops.Preseem.FleetIntelligence do
defp compute_capacity_ceiling(aps) do
aps
|> Enum.filter(fn ap -> ap.qoe_score != nil and ap.subscriber_count != nil end)
|> Enum.filter(fn ap -> ap.qoe_score >= 70 end)
|> Enum.filter(fn ap ->
ap.qoe_score != nil and ap.subscriber_count != nil and ap.qoe_score >= 70
end)
|> Enum.map(& &1.subscriber_count)
|> Enum.max(fn -> nil end)
end

View file

@ -112,24 +112,28 @@ defmodule Towerops.Preseem.Insights do
device_id: ap.device_id
})
existing =
Repo.get_by(Insight,
preseem_access_point_id: ap.id,
type: full_attrs.type,
status: "active"
)
if existing do
[]
else
case %Insight{} |> Insight.changeset(full_attrs) |> Repo.insert() do
{:ok, insight} -> [insight]
{:error, _} -> []
end
end
insert_insight_if_new(ap.id, full_attrs)
end)
end
defp insert_insight_if_new(ap_id, full_attrs) do
existing =
Repo.get_by(Insight,
preseem_access_point_id: ap_id,
type: full_attrs.type,
status: "active"
)
if existing do
[]
else
case %Insight{} |> Insight.changeset(full_attrs) |> Repo.insert() do
{:ok, insight} -> [insight]
{:error, _} -> []
end
end
end
defp check_qoe_degradation(ap, baselines) do
qoe_baseline = Enum.find(baselines, &(&1.metric_name == "qoe_score" and &1.period == "all"))

View file

@ -375,6 +375,10 @@ defmodule Towerops.Security.BruteForce do
defp create_cache_table do
:ets.new(@whitelist_cache_table, [:set, :public, :named_table, read_concurrency: true])
rescue
ArgumentError ->
# Table already exists due to race condition in async tests - this is fine
:ets.whereis(@whitelist_cache_table)
end
defp load_whitelist do

View file

@ -17,24 +17,7 @@ defmodule Towerops.Workers.PreseemSyncWorker do
def perform(%Oban.Job{}) do
integrations = Integrations.list_enabled_integrations("preseem")
results =
Enum.map(integrations, fn integration ->
if should_sync?(integration) do
case Sync.sync_organization(integration) do
{:ok, result} ->
Logger.info("Preseem sync completed for org #{integration.organization_id}: #{inspect(result)}")
{:ok, result}
{:error, reason} ->
Logger.error("Preseem sync failed for org #{integration.organization_id}: #{inspect(reason)}")
{:error, reason}
end
else
:skipped
end
end)
results = Enum.map(integrations, &sync_integration/1)
synced = Enum.count(results, &match?({:ok, _}, &1))
failed = Enum.count(results, &match?({:error, _}, &1))
@ -47,6 +30,22 @@ defmodule Towerops.Workers.PreseemSyncWorker do
:ok
end
defp sync_integration(integration) do
if should_sync?(integration) do
case Sync.sync_organization(integration) do
{:ok, result} ->
Logger.info("Preseem sync completed for org #{integration.organization_id}: #{inspect(result)}")
{:ok, result}
{:error, reason} ->
Logger.error("Preseem sync failed for org #{integration.organization_id}: #{inspect(reason)}")
{:error, reason}
end
else
:skipped
end
end
defp should_sync?(integration) do
case integration.last_synced_at do
nil ->

View file

@ -115,19 +115,8 @@ defmodule ToweropsWeb.Org.IntegrationsLive do
if api_key == "" or is_nil(api_key) do
{:noreply, assign(socket, :test_result, {:error, "Please enter an API key first"})}
else
case PreseemClient.test_connection(api_key) do
{:ok, _body} ->
{:noreply, assign(socket, :test_result, {:ok, "Connection successful"})}
{:error, :unauthorized} ->
{:noreply, assign(socket, :test_result, {:error, "Invalid API key"})}
{:error, :forbidden} ->
{:noreply, assign(socket, :test_result, {:error, "Access forbidden"})}
{:error, reason} ->
{:noreply, assign(socket, :test_result, {:error, "Connection failed: #{inspect(reason)}"})}
end
result = PreseemClient.test_connection(api_key)
{:noreply, assign(socket, :test_result, format_connection_result(result))}
end
end
@ -197,4 +186,23 @@ defmodule ToweropsWeb.Org.IntegrationsLive do
_ -> nil
end
end
defp format_connection_result({:ok, _body}), do: {:ok, "Connection successful"}
defp format_connection_result({:error, :unauthorized}), do: {:error, "Invalid API key"}
defp format_connection_result({:error, :forbidden}), do: {:error, "Access forbidden"}
defp format_connection_result({:error, {:unexpected_status, status}}),
do: {:error, "API returned unexpected status: #{status}"}
defp format_connection_result({:error, %{reason: :timeout}}),
do: {:error, "Connection timeout - unable to reach Preseem API"}
defp format_connection_result({:error, %{reason: :econnrefused}}),
do: {:error, "Connection refused - check API endpoint"}
defp format_connection_result({:error, %{reason: :nxdomain}}), do: {:error, "DNS lookup failed - check API endpoint"}
# Never expose internal error details to users
defp format_connection_result({:error, _reason}),
do: {:error, "Connection failed - please check your API credentials and try again"}
end

View file

@ -32,7 +32,7 @@ defmodule Towerops.Preseem.BaselineTest do
assert {:ok, 1} = Baseline.compute_baselines(org.id)
baselines = Repo.all(DeviceBaseline)
assert length(baselines) > 0
refute baselines == []
latency_baseline = Enum.find(baselines, &(&1.metric_name == "avg_latency"))
assert latency_baseline

View file

@ -40,7 +40,7 @@ defmodule Towerops.Snmp.SensorChangeDetectorTest do
%{device: device, snmp_device: snmp_device}
end
defp create_sensor(snmp_device, attrs \\ %{}) do
defp create_sensor(snmp_device, attrs) do
defaults = %{
snmp_device_id: snmp_device.id,
sensor_type: "frequency",

View file

@ -1,6 +1,9 @@
defmodule ToweropsNativeTest do
use ExUnit.Case, async: false
# Suppress false positive dialyzer warnings for NIF return types
@dialyzer :no_match
# NIF not compiled in this worktree — skip all tests
@moduletag :skip
@ -104,7 +107,7 @@ defmodule ToweropsNativeTest do
oid1 = ToweropsNative.resolve_oid("SNMPv2-MIB::sysDescr")
oid2 = ToweropsNative.resolve_oid("sysDescr")
# Both should resolve to the same OID
# Both should resolve to the same OID (dialyzer can't infer these always succeed)
assert is_binary(oid1)
assert is_binary(oid2)
assert oid1 == oid2

View file

@ -1206,7 +1206,6 @@ defmodule ToweropsWeb.AgentChannelTest do
describe "device deletion and job refresh" do
test "agent receives updated job list after device deletion", %{
socket: socket,
device: device,
agent_token: agent_token
} do
@ -1280,7 +1279,7 @@ defmodule ToweropsWeb.AgentChannelTest do
# (This is verified by the channel staying alive)
end
test "multiple rapid assignment changes are debounced", %{socket: socket, agent_token: agent_token} do
test "multiple rapid assignment changes are debounced", %{agent_token: agent_token} do
# Trigger multiple rapid assignment changes
for i <- 1..5 do
Phoenix.PubSub.broadcast(

View file

@ -7,10 +7,13 @@ defmodule ToweropsWeb.SiteLiveTest do
setup :register_and_log_in_user
setup %{user: user} do
setup %{conn: conn, user: user} do
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
%{organization: organization}
# Set the current organization in the session to ensure correct scope
conn = Plug.Conn.put_session(conn, :current_organization_id, organization.id)
%{conn: conn, organization: organization}
end
describe "Index" do