Promoted pure presentation and utility helpers from `defp` to `def @doc false` across ~20 LiveViews, Oban workers, and sync modules so they're reachable from unit tests. Refactored several `cond` blocks into idiomatic function heads with guards. Added ~250 new test cases in new files under test/towerops and test/towerops_web, including DB-backed tests for CnMaestro.Sync and AlertNotificationWorker, and removed dead LiveView tab components and CapacityLive (no callers anywhere in lib/test). Configured mix.exs test_coverage.ignore_modules to exclude vendored third-party code (SnmpKit, protobuf-generated Towerops.Agent.*, Absinthe GraphQL types, Phoenix HTML modules, Inspect protocol impls) from coverage calculations — these are not our project code. Coverage: 66.93% → 70.09%. Full suite: 10,127 tests, 0 failures.
189 lines
6.2 KiB
Elixir
189 lines
6.2 KiB
Elixir
defmodule Towerops.TraceTest do
|
|
use Towerops.DataCase, async: true
|
|
|
|
import Towerops.AccountsFixtures
|
|
|
|
alias Towerops.Trace
|
|
|
|
setup do
|
|
user = user_fixture()
|
|
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test ISP"}, user.id)
|
|
|
|
{:ok, site} =
|
|
Towerops.Sites.create_site(%{
|
|
name: "Tower Alpha",
|
|
organization_id: organization.id,
|
|
location: "Downtown"
|
|
})
|
|
|
|
{:ok, device} =
|
|
Towerops.Devices.create_device(%{
|
|
name: "Router-1",
|
|
ip_address: "10.0.0.1",
|
|
site_id: site.id,
|
|
organization_id: organization.id,
|
|
snmp_enabled: true
|
|
})
|
|
|
|
%{organization: organization, site: site, device: device}
|
|
end
|
|
|
|
describe "search/2" do
|
|
test "finds sites by name", %{organization: organization} do
|
|
results = Trace.search(organization.id, "Tower")
|
|
|
|
site_results = Enum.filter(results, &(&1.type == :site))
|
|
assert length(site_results) == 1
|
|
assert hd(site_results).label == "Tower Alpha"
|
|
end
|
|
|
|
test "finds sites by location", %{organization: organization} do
|
|
results = Trace.search(organization.id, "Downtown")
|
|
|
|
site_results = Enum.filter(results, &(&1.type == :site))
|
|
assert length(site_results) == 1
|
|
end
|
|
|
|
test "finds devices by name", %{organization: organization} do
|
|
results = Trace.search(organization.id, "Router")
|
|
|
|
device_results = Enum.filter(results, &(&1.type == :device))
|
|
assert length(device_results) == 1
|
|
assert hd(device_results).label == "Router-1"
|
|
end
|
|
|
|
test "returns empty list for short queries", %{organization: organization} do
|
|
assert Trace.search(organization.id, "a") == []
|
|
end
|
|
end
|
|
|
|
describe "assemble_trace/3" do
|
|
test "assembles trace from site with its devices", %{
|
|
organization: organization,
|
|
site: site,
|
|
device: device
|
|
} do
|
|
trace = Trace.assemble_trace(organization.id, :site, site.id)
|
|
|
|
assert trace
|
|
assert trace.site.id == site.id
|
|
assert trace.site.name == "Tower Alpha"
|
|
assert length(trace.devices) == 1
|
|
assert hd(trace.devices).id == device.id
|
|
end
|
|
|
|
test "returns nil for non-existent site", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :site, Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "assembles trace from device", %{organization: organization, device: device} do
|
|
trace = Trace.assemble_trace(organization.id, :device, device.id)
|
|
|
|
assert trace
|
|
assert trace.device.id == device.id
|
|
# No account/sub
|
|
assert trace.subscriber == nil
|
|
end
|
|
|
|
test "returns nil for non-existent device", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :device, Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "returns nil for non-existent account", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :account, Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "returns nil for non-existent inventory item", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :inventory_item, Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "returns nil for non-existent access_point", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :access_point, Ecto.UUID.generate()) == nil
|
|
end
|
|
|
|
test "returns nil for unknown type", %{organization: organization} do
|
|
assert Trace.assemble_trace(organization.id, :unknown, Ecto.UUID.generate()) == nil
|
|
end
|
|
end
|
|
|
|
describe "format_address/1" do
|
|
test "returns nil for nil" do
|
|
assert Trace.format_address(nil) == nil
|
|
end
|
|
|
|
test "returns nil for empty map" do
|
|
assert Trace.format_address(%{}) == nil
|
|
end
|
|
|
|
test "returns nil for non-map" do
|
|
assert Trace.format_address("string") == nil
|
|
assert Trace.format_address(123) == nil
|
|
assert Trace.format_address(:atom) == nil
|
|
end
|
|
|
|
test "joins present components with commas" do
|
|
addr = %{"street1" => "123 Main", "city" => "Springfield", "state" => "IL", "zip" => "62701"}
|
|
assert "123 Main, Springfield, IL, 62701" == Trace.format_address(addr)
|
|
end
|
|
|
|
test "prefers street1 over line1 and state over province" do
|
|
addr = %{"street1" => "first", "line1" => "alt", "state" => "S", "province" => "P"}
|
|
result = Trace.format_address(addr)
|
|
assert String.contains?(result, "first")
|
|
refute String.contains?(result, "alt")
|
|
assert String.contains?(result, "S")
|
|
end
|
|
|
|
test "falls back to line1 when street1 is missing" do
|
|
addr = %{"line1" => "fallback", "city" => "City"}
|
|
assert "fallback, City" == Trace.format_address(addr)
|
|
end
|
|
|
|
test "falls back to province/postal_code when state/zip absent" do
|
|
addr = %{"city" => "Montreal", "province" => "QC", "postal_code" => "H3A"}
|
|
assert "Montreal, QC, H3A" == Trace.format_address(addr)
|
|
end
|
|
|
|
test "trims whitespace-only as blank" do
|
|
addr = %{"street1" => " ", "city" => "Real"}
|
|
assert "Real" == Trace.format_address(addr)
|
|
end
|
|
|
|
test "skips nil components" do
|
|
addr = %{"street1" => nil, "city" => "Only"}
|
|
assert "Only" == Trace.format_address(addr)
|
|
end
|
|
|
|
test "handles line2/street2 (included when set)" do
|
|
addr = %{"street1" => "a", "street2" => "b", "city" => "c"}
|
|
assert "a, b, c" == Trace.format_address(addr)
|
|
end
|
|
end
|
|
|
|
describe "search/2 (edge cases)" do
|
|
test "returns empty list for whitespace query", %{organization: organization} do
|
|
assert Trace.search(organization.id, " ") == []
|
|
assert Trace.search(organization.id, "\t\n") == []
|
|
end
|
|
|
|
test "matches device by IP", %{organization: organization} do
|
|
results = Trace.search(organization.id, "10.0.0")
|
|
device_results = Enum.filter(results, &(&1.type == :device))
|
|
refute device_results == []
|
|
end
|
|
|
|
test "caps total results at 25", %{organization: organization, site: site} do
|
|
for i <- 1..30 do
|
|
Towerops.DevicesFixtures.device_fixture(%{
|
|
name: "Queryable-#{i}",
|
|
ip_address: "10.99.0.#{i}",
|
|
site_id: site.id,
|
|
organization_id: organization.id
|
|
})
|
|
end
|
|
|
|
results = Trace.search(organization.id, "Queryable")
|
|
assert Enum.count(results) <= 25
|
|
end
|
|
end
|
|
end
|