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.
131 lines
4 KiB
Elixir
131 lines
4 KiB
Elixir
defmodule Towerops.Sonar.SyncTest do
|
|
use ExUnit.Case, async: true
|
|
|
|
alias Towerops.Sonar.Sync
|
|
|
|
describe "sum_service_prices/1" do
|
|
test "sums all numeric price_override values" do
|
|
services = [
|
|
%{"price_override" => 10.0},
|
|
%{"price_override" => 25.5},
|
|
%{"price_override" => 100}
|
|
]
|
|
|
|
assert 135.5 == Sync.sum_service_prices(services)
|
|
end
|
|
|
|
test "ignores nil price_override" do
|
|
services = [%{"price_override" => 10.0}, %{"price_override" => nil}]
|
|
assert 10.0 == Sync.sum_service_prices(services)
|
|
end
|
|
|
|
test "ignores non-numeric price_override" do
|
|
services = [
|
|
%{"price_override" => "not a number"},
|
|
%{"price_override" => 5.0},
|
|
%{"other_field" => "ignored"}
|
|
]
|
|
|
|
assert 5.0 == Sync.sum_service_prices(services)
|
|
end
|
|
|
|
test "returns 0.0 for empty list" do
|
|
assert 0.0 == Sync.sum_service_prices([])
|
|
end
|
|
|
|
test "handles integer and float mixed" do
|
|
services = [%{"price_override" => 10}, %{"price_override" => 2.5}]
|
|
assert 12.5 == Sync.sum_service_prices(services)
|
|
end
|
|
end
|
|
|
|
describe "humanize_sync_error/1" do
|
|
test "unauthorized" do
|
|
assert Sync.humanize_sync_error(:unauthorized) =~ "Authentication failed"
|
|
end
|
|
|
|
test "forbidden" do
|
|
assert Sync.humanize_sync_error(:forbidden) =~ "Access denied"
|
|
end
|
|
|
|
test "rate_limited includes retry seconds" do
|
|
assert Sync.humanize_sync_error({:rate_limited, 120}) =~ "retry after 120s"
|
|
end
|
|
|
|
test "unexpected_status includes status code" do
|
|
assert Sync.humanize_sync_error({:unexpected_status, 502}) =~ "HTTP 502"
|
|
end
|
|
|
|
test "graphql_errors joins messages" do
|
|
errors = [%{"message" => "bad field"}, %{"message" => "missing arg"}]
|
|
msg = Sync.humanize_sync_error({:graphql_errors, errors})
|
|
assert msg =~ "GraphQL errors"
|
|
assert msg =~ "bad field"
|
|
assert msg =~ "missing arg"
|
|
assert msg =~ ";"
|
|
end
|
|
|
|
test "graphql_errors without message uses inspect" do
|
|
errors = [%{"foo" => "bar"}]
|
|
msg = Sync.humanize_sync_error({:graphql_errors, errors})
|
|
assert msg =~ "GraphQL errors"
|
|
assert msg =~ "%{"
|
|
end
|
|
|
|
test "SSL-related string errors return SSL message" do
|
|
for snippet <- ["CA trust store", "cacert", "certificate"] do
|
|
msg = Sync.humanize_sync_error("failure: #{snippet} something")
|
|
assert msg =~ "SSL certificate verification failed"
|
|
end
|
|
end
|
|
|
|
test "other string errors return generic message" do
|
|
assert Sync.humanize_sync_error("random broken thing") ==
|
|
"An unexpected error occurred during sync"
|
|
end
|
|
|
|
test "non-binary unknown errors return generic message" do
|
|
assert Sync.humanize_sync_error(:something_weird) ==
|
|
"An unexpected error occurred during sync"
|
|
|
|
assert Sync.humanize_sync_error({:unknown_tuple, 42}) ==
|
|
"An unexpected error occurred during sync"
|
|
end
|
|
end
|
|
|
|
describe "add_price/2" do
|
|
test "adds a number to the accumulator" do
|
|
assert 15.5 == Sync.add_price(10, 5.5)
|
|
assert 20 == Sync.add_price(5, 15)
|
|
end
|
|
|
|
test "nil price leaves accumulator unchanged" do
|
|
assert 10 == Sync.add_price(10, nil)
|
|
end
|
|
|
|
test "non-numeric price leaves accumulator unchanged" do
|
|
assert 10 == Sync.add_price(10, "$5")
|
|
assert 10.0 == Sync.add_price(10.0, :invalid)
|
|
end
|
|
end
|
|
|
|
describe "calculate_total_mrr/1" do
|
|
test "sums services across all accounts" do
|
|
accounts = [
|
|
%{"account_services" => [%{"price_override" => 10.0}, %{"price_override" => 5.0}]},
|
|
%{"account_services" => [%{"price_override" => 20}]}
|
|
]
|
|
|
|
assert 35.0 == Sync.calculate_total_mrr(accounts)
|
|
end
|
|
|
|
test "handles missing account_services key" do
|
|
accounts = [%{"name" => "no services"}, %{"account_services" => [%{"price_override" => 10.0}]}]
|
|
assert 10.0 == Sync.calculate_total_mrr(accounts)
|
|
end
|
|
|
|
test "handles empty list" do
|
|
assert 0.0 == Sync.calculate_total_mrr([])
|
|
end
|
|
end
|
|
end
|