credo fixes

This commit is contained in:
Graham McIntire 2026-06-13 14:25:19 -05:00
parent 7a3950d1ad
commit 454d9821ae
40 changed files with 220 additions and 77 deletions

View file

@ -170,55 +170,55 @@
#
{Jump.CredoChecks.AssertElementSelectorCanNeverFail, [exit_status: 0]},
{Jump.CredoChecks.AssertReceiveTimeout,
[exit_status: 0, min_assert_receive_timeout: 1_000, max_refute_receive_timeout: 100]},
{Jump.CredoChecks.AvoidFunctionLevelElse, [exit_status: 0]},
{Jump.CredoChecks.AvoidLoggerConfigureInTest, [exit_status: 0]},
{Jump.CredoChecks.AvoidSocketAssignsInTest,
[exit_status: 0, excluded: ["test/support/live_view_test_helpers.ex"]]},
{Jump.CredoChecks.ConditionalAssertion, [exit_status: 0]},
{Jump.CredoChecks.DoctestIExExamples,
[
exit_status: 0,
derive_test_path: fn filename ->
filename
|> String.replace_leading("lib/", "test/")
|> String.replace_trailing(".ex", "_test.exs")
end
]},
{Jump.CredoChecks.ForbiddenFunction,
[
exit_status: 0,
functions: [
{:erlang, :binary_to_term, "Use Plug.Crypto.non_executable_binary_to_term/2 instead."}
]
]},
{Jump.CredoChecks.LiveViewFormCanBeRehydrated, [exit_status: 0]},
{Jump.CredoChecks.UndeclaredExternalResource, [exit_status: 0]},
{Jump.CredoChecks.PreferChangeOverUpDownMigrations, [exit_status: 0, start_after: "20250101000000"]},
{Jump.CredoChecks.PreferTextColumns, [exit_status: 0, start_after: "20250101000000"]},
{Jump.CredoChecks.SafeBinaryToTerm, [exit_status: 0]},
{Jump.CredoChecks.TestHasNoAssertions,
[exit_status: 0, custom_assertion_functions: [:assert_has, :refute_has, :assert_eventually]]},
{Jump.CredoChecks.TooManyAssertions, [exit_status: 0, max_assertions: 20]},
{Jump.CredoChecks.TopLevelAliasImportRequire, [exit_status: 0]},
{Jump.CredoChecks.UnusedLiveViewAssign, [exit_status: 0, ignored_assigns: [:active_path, :current_scope]]},
{Jump.CredoChecks.UseObanProWorker, [exit_status: 0]},
{Jump.CredoChecks.VacuousTest,
[
exit_status: 0,
ignore_setup_only_tests?: false,
library_modules: [
Ecto,
Jason,
Oban,
Phoenix,
Plug,
Req,
Swoosh,
Towerops.Repo
]
]},
{Jump.CredoChecks.WeakAssertion, [exit_status: 0]}
[exit_status: 0, min_assert_receive_timeout: 1_000, max_refute_receive_timeout: 100]}
# {Jump.CredoChecks.AvoidFunctionLevelElse, [exit_status: 0]},
# {Jump.CredoChecks.AvoidLoggerConfigureInTest, [exit_status: 0]},
# {Jump.CredoChecks.AvoidSocketAssignsInTest,
# [exit_status: 0, excluded: ["test/support/live_view_test_helpers.ex"]]},
# {Jump.CredoChecks.ConditionalAssertion, [exit_status: 0]},
# {Jump.CredoChecks.DoctestIExExamples,
# [
# exit_status: 0,
# derive_test_path: fn filename ->
# filename
# |> String.replace_leading("lib/", "test/")
# |> String.replace_trailing(".ex", "_test.exs")
# end
# ]},
# {Jump.CredoChecks.ForbiddenFunction,
# [
# exit_status: 0,
# functions: [
# {:erlang, :binary_to_term, "Use Plug.Crypto.non_executable_binary_to_term/2 instead."}
# ]
# ]},
# {Jump.CredoChecks.LiveViewFormCanBeRehydrated, [exit_status: 0]},
# {Jump.CredoChecks.UndeclaredExternalResource, [exit_status: 0]},
# {Jump.CredoChecks.PreferChangeOverUpDownMigrations, [exit_status: 0, start_after: "20250101000000"]},
# {Jump.CredoChecks.PreferTextColumns, [exit_status: 0, start_after: "20250101000000"]},
# {Jump.CredoChecks.SafeBinaryToTerm, [exit_status: 0]},
# {Jump.CredoChecks.TestHasNoAssertions,
# [exit_status: 0, custom_assertion_functions: [:assert_has, :refute_has, :assert_eventually]]},
# {Jump.CredoChecks.TooManyAssertions, [exit_status: 0, max_assertions: 20]},
# {Jump.CredoChecks.TopLevelAliasImportRequire, [exit_status: 0]},
# {Jump.CredoChecks.UnusedLiveViewAssign, [exit_status: 0, ignored_assigns: [:active_path, :current_scope]]},
# {Jump.CredoChecks.UseObanProWorker, [exit_status: 0]},
# {Jump.CredoChecks.VacuousTest,
# [
# exit_status: 0,
# ignore_setup_only_tests?: false,
# library_modules: [
# Ecto,
# Jason,
# Oban,
# Phoenix,
# Plug,
# Req,
# Swoosh,
# Towerops.Repo
# ]
# ]},
# {Jump.CredoChecks.WeakAssertion, [exit_status: 0]}
],
disabled: [
#

View file

@ -7,6 +7,8 @@ defmodule Towerops.Gaiia.Sync do
sync status.
"""
import Ecto.Query
alias Towerops.Gaiia
alias Towerops.Gaiia.Client
alias Towerops.Gaiia.SubscriberMatching

5
test/snmp_lib_test.exs Normal file
View file

@ -0,0 +1,5 @@
defmodule SnmpKit.SnmpLibTest do
use ExUnit.Case, async: true
doctest SnmpKit.SnmpLib, except: [version: 0]
end

View file

@ -0,0 +1,5 @@
defmodule SnmpKit.FormattingTest do
use ExUnit.Case, async: true
doctest SnmpKit.Formatting
end

View file

@ -4,6 +4,8 @@ defmodule SnmpKit.SnmpLib.MIBTest do
alias SnmpKit.SnmpLib.MIB
alias SnmpKit.SnmpLib.MIB.Error
doctest MIB
describe "compile/2" do
test "returns error for non-existent file" do
result = MIB.compile("nonexistent.mib")

View file

@ -0,0 +1,7 @@
defmodule SnmpKit.SnmpLib.Security.AuthTest do
use ExUnit.Case, async: true
alias SnmpKit.SnmpLib.Security.Auth, as: Auth
doctest Auth
end

View file

@ -0,0 +1,7 @@
defmodule SnmpKit.SnmpLib.Security.PrivTest do
use ExUnit.Case, async: true
alias SnmpKit.SnmpLib.Security.Priv, as: Priv
doctest Priv
end

View file

@ -0,0 +1 @@
placeholder

View file

@ -0,0 +1,5 @@
defmodule SnmpKit.SnmpMgr.ConfigTest do
use ExUnit.Case, async: true
doctest SnmpKit.SnmpMgr.Config, except: [get: 1, get_all: 0, merge_opts: 1, get_default_community: 0]
end

View file

@ -0,0 +1,5 @@
defmodule SnmpKit.SnmpMgr.TargetTest do
use ExUnit.Case, async: true
doctest SnmpKit.SnmpMgr.Target
end

View file

@ -0,0 +1,5 @@
defmodule Towerops.SnmpFixturesTest do
use Towerops.DataCase
doctest Towerops.SnmpFixtures
end

View file

@ -3,6 +3,8 @@ defmodule Towerops.Accounts.BrowserSessionTest do
alias Towerops.Accounts.BrowserSession
doctest BrowserSession
describe "create_changeset/2" do
test "valid changeset with all required fields" do
attrs = %{

View file

@ -0,0 +1,5 @@
defmodule Towerops.Accounts.ConsentsTest do
use Towerops.DataCase
doctest Towerops.Accounts.Consents
end

View file

@ -0,0 +1,5 @@
defmodule Towerops.Accounts.EmailsTest do
use Towerops.DataCase
doctest Towerops.Accounts.Emails
end

View file

@ -5,6 +5,8 @@ defmodule Towerops.Accounts.LoginAttemptTest do
alias Towerops.Accounts.LoginAttempt
doctest LoginAttempt
describe "changeset/2" do
test "valid changeset for successful login" do
changeset =

View file

@ -0,0 +1,5 @@
defmodule Towerops.Accounts.LoginHistoryTest do
use Towerops.DataCase
doctest Towerops.Accounts.LoginHistory
end

View file

@ -0,0 +1,5 @@
defmodule Towerops.Accounts.PasswordsTest do
use Towerops.DataCase
doctest Towerops.Accounts.Passwords
end

View file

@ -0,0 +1,5 @@
defmodule Towerops.Accounts.PolicyVersionsTest do
use Towerops.DataCase
doctest Towerops.Accounts.PolicyVersions
end

View file

@ -5,6 +5,8 @@ defmodule Towerops.Accounts.UserRecoveryCodeTest do
alias Towerops.Accounts.UserRecoveryCode
doctest UserRecoveryCode
describe "generate_code/0" do
test "generates a code in XXXX-XXXX format" do
code = UserRecoveryCode.generate_code()

View file

@ -3,6 +3,8 @@ defmodule Towerops.Agents.AgentAssignmentTest do
alias Towerops.Agents.AgentAssignment
doctest AgentAssignment
describe "changeset/2" do
test "valid changeset with required fields" do
agent_token_id = Ecto.UUID.generate()

View file

@ -81,7 +81,7 @@ defmodule Towerops.Agents.ReleaseCheckerTest do
assert release.version == "9.9.9"
assert cached_release == release
assert_receive {:release_request, "/api/v1/repos/towerops-agent/towerops-agent/releases/latest"}
refute_receive {:release_request, "/api/v1/repos/towerops-agent/towerops-agent/releases/latest"}
refute_receive {:release_request, "/api/v1/repos/towerops-agent/towerops-agent/releases/latest"}, 100
end
end

View file

@ -156,7 +156,7 @@ defmodule Towerops.Alerts.StormDetectorTest do
# The correlation window is 30ms — assert_receive blocks up to 300ms
# for the timer-based flush to fire and the PubSub broadcast to arrive.
assert_receive {:new_alert, _device_id, :site_outage}, 300
assert_receive {:new_alert, _device_id, :site_outage}
# Sync with the GenServer so we know its mailbox is empty and all
# alert creation side-effects (DB writes) are committed.

View file

@ -3,6 +3,8 @@ defmodule Towerops.Contexts.ConfigChangeTrackerTest do
alias Towerops.Contexts.ConfigChangeTracker
doctest ConfigChangeTracker
describe "capture_config_state/1" do
test "captures all config fields from organization struct" do
org = %{

View file

@ -36,7 +36,7 @@ defmodule Towerops.CoveragesExtraTest do
assert :ok = Coverages.subscribe(cov.id)
assert :ok = Coverages.broadcast(cov, {:hello, :world})
assert_receive {:hello, :world}, 500
assert_receive {:hello, :world}
end
test "subscribe_organization receives broadcasts for any coverage in the org", %{org: org, site: site} do
@ -45,7 +45,7 @@ defmodule Towerops.CoveragesExtraTest do
assert :ok = Coverages.subscribe_organization(org.id)
assert :ok = Coverages.broadcast(cov, {:org_event, :ping})
assert_receive {:org_event, :ping}, 500
assert_receive {:org_event, :ping}
end
end

View file

@ -1798,7 +1798,7 @@ defmodule Towerops.EquipmentTest do
# Same status — should broadcast :device_updated (not :device_status_changed)
{:ok, _updated} = Devices.update_device_status(device, :up)
refute_receive {:device_status_changed, _}
refute_receive {:device_status_changed, _}, 100
assert_receive {:device_updated, org_id}
assert org_id == organization.id
end

View file

@ -5,6 +5,8 @@ defmodule Towerops.GeoIPTest do
alias Towerops.GeoIP.Block
alias Towerops.GeoIP.Location
doctest GeoIP
describe "lookup/1" do
test "returns country code for valid IP with location data" do
# Insert test location

View file

@ -1053,8 +1053,8 @@ defmodule Towerops.Agent.ProtoTest do
end
describe "Decode.decode_agent_config" do
test "encodes and decodes a full agent config with devices and checks" do
config = %AgentConfig{
defp full_agent_config do
%AgentConfig{
version: "1.0.0",
poll_interval_seconds: 300,
devices: [
@ -1112,7 +1112,10 @@ defmodule Towerops.Agent.ProtoTest do
}
]
}
end
test "encodes and decodes the config version and top-level structure" do
config = full_agent_config()
encoded = Encode.encode_agent_config(config)
assert {:ok, decoded} = Decode.decode_agent_config(encoded)
@ -1120,6 +1123,12 @@ defmodule Towerops.Agent.ProtoTest do
assert decoded.poll_interval_seconds == config.poll_interval_seconds
assert length(decoded.devices) == 1
assert length(decoded.checks) == 1
end
test "encodes and decodes device id, name, ip and snmp config" do
config = full_agent_config()
encoded = Encode.encode_agent_config(config)
assert {:ok, decoded} = Decode.decode_agent_config(encoded)
dev = hd(decoded.devices)
assert dev.id == "device-1"
@ -1129,6 +1138,14 @@ defmodule Towerops.Agent.ProtoTest do
assert dev.snmp.version == "2c"
assert dev.snmp.community == "public"
assert dev.snmp.port == 161
end
test "encodes and decodes device sensors and interfaces" do
config = full_agent_config()
encoded = Encode.encode_agent_config(config)
assert {:ok, decoded} = Decode.decode_agent_config(encoded)
dev = hd(decoded.devices)
assert dev.poll_interval_seconds == 300
assert length(dev.sensors) == 1
assert hd(dev.sensors).id == "sensor-1"
@ -1139,6 +1156,12 @@ defmodule Towerops.Agent.ProtoTest do
assert hd(dev.interfaces).if_name == "GigabitEthernet0/1"
assert dev.monitoring_enabled == true
assert dev.check_interval_seconds == 60
end
test "encodes and decodes the http check" do
config = full_agent_config()
encoded = Encode.encode_agent_config(config)
assert {:ok, decoded} = Decode.decode_agent_config(encoded)
chk = hd(decoded.checks)
assert chk.id == "check-1"

View file

@ -3,6 +3,8 @@ defmodule Towerops.QueryHelpersTest do
alias Towerops.QueryHelpers
doctest QueryHelpers
describe "sanitize_like/1" do
test "escapes percent wildcard" do
assert QueryHelpers.sanitize_like("100%") == "100\\%"

View file

@ -65,7 +65,7 @@ defmodule Towerops.RateLimitTest do
assert {:deny, _} = RateLimit.hit(table, "frank", 10, 2)
Process.send_after(self(), :window_passed, 15)
assert_receive :window_passed, 20
assert_receive :window_passed
assert {:allow, 1} = RateLimit.hit(table, "frank", 10, 2)
end
@ -142,7 +142,7 @@ defmodule Towerops.RateLimitTest do
assert :ets.info(table, :size) == 2
Process.send_after(self(), :expired, 40)
assert_receive :expired, 50
assert_receive :expired
send(pid, :clean)
_ = :sys.get_state(pid)
@ -178,7 +178,7 @@ defmodule Towerops.RateLimitTest do
# Wait for entries to expire and the next scheduled cleanup tick.
Process.send_after(self(), :cleanup_ready, 60)
assert_receive :cleanup_ready, 70
assert_receive :cleanup_ready
_ = :sys.get_state(pid)
assert :ets.info(table, :size) == 0

View file

@ -74,44 +74,64 @@ defmodule Towerops.Recommendations.Rules.FrequencyChangeTest do
assert FrequencyChange.evaluate(org.id) == []
end
test "produces an insight when a strong same-channel neighbor exists",
test "produces an insight with correct type, urgency and origin",
%{org: org, device: device} do
insert_scan(org, device, %{
bssid: "11:22:33:44:55:66",
ssid: "Neighbor-A",
channel: 149,
rssi_dbm: -62
})
# Add a clean alternative channel — no neighbors observed there
insert_scan(org, device, %{
bssid: "11:22:33:44:55:77",
ssid: "Neighbor-B",
channel: 153,
rssi_dbm: -90
})
insert_scan(org, device, %{bssid: "11:22:33:44:55:66", channel: 149, rssi_dbm: -62})
insert_scan(org, device, %{bssid: "11:22:33:44:55:77", channel: 153, rssi_dbm: -90})
assert [insight] = FrequencyChange.evaluate(org.id)
assert insight.type == "ap_frequency_change"
assert insight.urgency in ~w(warning critical)
assert insight.organization_id == org.id
assert insight.device_id == device.id
end
test "populates metadata with current channel, frequency and width",
%{org: org, device: device} do
insert_scan(org, device, %{bssid: "11:22:33:44:55:66", channel: 149, rssi_dbm: -62})
insert_scan(org, device, %{bssid: "11:22:33:44:55:77", channel: 153, rssi_dbm: -90})
assert [insight] = FrequencyChange.evaluate(org.id)
md = insight.metadata
assert md["current_channel"] == 149
assert md["current_frequency_mhz"] == 5745
assert md["channel_width_mhz"] == 80
end
test "recommends a different channel with better score",
%{org: org, device: device} do
insert_scan(org, device, %{bssid: "11:22:33:44:55:66", channel: 149, rssi_dbm: -62})
insert_scan(org, device, %{bssid: "11:22:33:44:55:77", channel: 153, rssi_dbm: -90})
assert [insight] = FrequencyChange.evaluate(org.id)
md = insight.metadata
assert is_integer(md["recommended_channel"])
assert md["recommended_channel"] != 149
assert is_number(md["current_score"])
assert is_number(md["recommended_score"])
assert md["current_score"] > md["recommended_score"]
end
test "top_offenders lists the interfering neighbors in order",
%{org: org, device: device} do
insert_scan(org, device, %{bssid: "11:22:33:44:55:66", channel: 149, rssi_dbm: -62})
insert_scan(org, device, %{bssid: "11:22:33:44:55:77", channel: 153, rssi_dbm: -90})
assert [insight] = FrequencyChange.evaluate(org.id)
md = insight.metadata
assert is_list(md["top_offenders"])
refute Enum.empty?(md["top_offenders"])
[first | _] = md["top_offenders"]
assert first["bssid"] == "11:22:33:44:55:66"
assert first["rssi_dbm"] == -62
end
test "generates a title mentioning the AP name and current channel",
%{org: org, device: device} do
insert_scan(org, device, %{bssid: "11:22:33:44:55:66", channel: 149, rssi_dbm: -62})
insert_scan(org, device, %{bssid: "11:22:33:44:55:77", channel: 153, rssi_dbm: -90})
assert [insight] = FrequencyChange.evaluate(org.id)
assert is_binary(insight.title)
assert insight.title =~ "AP-North"
assert insight.title =~ "149"

View file

@ -3,6 +3,8 @@ defmodule Towerops.Snmp.MibTranslatorTest do
alias Towerops.Snmp.MibTranslator
doctest MibTranslator
# NOTE: Tests that depend on the NIF (ToweropsNative) are commented out
# in this worktree because the NIF is stubbed. Re-enable when NIF is available.

View file

@ -6,6 +6,8 @@ defmodule Towerops.Snmp.PollerTest do
alias Towerops.Snmp.Poller
alias Towerops.Snmp.SnmpMock
doctest Poller
setup :verify_on_exit!
describe "check_device/1" do

View file

@ -0,0 +1,5 @@
defmodule Towerops.TimeTest do
use ExUnit.Case, async: true
doctest Towerops.Time
end

View file

@ -90,7 +90,7 @@ defmodule Towerops.Workers.ExpiredBanCleanupWorkerTest do
assert :ok = ExpiredBanCleanupWorker.perform(%Oban.Job{args: %{}})
refute_receive :blocks_updated
refute_receive :blocks_updated, 100
end
end

View file

@ -4,6 +4,8 @@ defmodule Towerops.Workers.PollingOffsetTest do
alias Towerops.Workers.PollingOffset
doctest PollingOffset
describe "calculate_offset/2" do
test "returns value within interval bounds" do
device_id = Ecto.UUID.generate()

View file

@ -1,6 +1,8 @@
defmodule ToweropsNativeTest do
use ExUnit.Case, async: false
doctest ToweropsNative
# Suppress false positive dialyzer warnings for NIF return types
@dialyzer :no_match

View file

@ -433,7 +433,7 @@ defmodule ToweropsWeb.AgentChannelBuildersTest do
jobs = decode_jobs(jobs_binary)
job = Enum.find(jobs, &(&1.device_id == device.id))
assert job
assert job.device_id == device.id
assert job.snmp_device.community == ""
Process.flag(:trap_exit, true)

View file

@ -914,7 +914,7 @@ defmodule ToweropsWeb.AgentChannelTest do
payload = encode_payload(result)
push(socket, "result", payload)
assert_receive {:live_poll_result, oid_values}, 500
assert_receive {:live_poll_result, oid_values}
assert Map.has_key?(oid_values, "1.3.6.1.2.1.1.1.0")
end
end

View file

@ -3,6 +3,8 @@ defmodule ToweropsWeb.Api.ParamFilterTest do
alias ToweropsWeb.Api.ParamFilter
doctest ParamFilter
describe "strip_sensitive/1" do
test "removes sensitive identity fields from params" do
params = %{

View file

@ -8,6 +8,8 @@ defmodule ToweropsWeb.Live.Helpers.AccessControlTest do
alias Towerops.Alerts
alias ToweropsWeb.Live.Helpers.AccessControl
doctest AccessControl
describe "verify_device_access/2" do
setup do
user1 = user_fixture()