towerops/test/towerops/profiles_test.exs
Graham McIntire d34b8b0f3f
Add comprehensive test coverage for core context modules
Improves test coverage toward 90% target by adding tests for:
- Organizations context (bulk SNMP/agent operations)
- Sites context (hierarchical structure and bulk operations)
- Profiles context and schemas (device/sensor OID management)
- EctoTypes.JsonAny custom type
- Snmp.Neighbor schema (LLDP/CDP topology)

All new tests validate changeset validations, business logic,
and database constraints to ensure data integrity.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-19 14:38:34 -06:00

346 lines
10 KiB
Elixir

defmodule Towerops.ProfilesTest do
use Towerops.DataCase, async: true
alias Towerops.Profiles
alias Towerops.Profiles.DeviceProfile
describe "list_profiles/0" do
test "returns all enabled profiles ordered by priority" do
{:ok, profile1} =
Profiles.create_profile(%{
name: "High Priority",
vendor: "Cisco",
priority: 10,
enabled: true
})
{:ok, profile2} =
Profiles.create_profile(%{
name: "Low Priority",
vendor: "MikroTik",
priority: 100,
enabled: true
})
{:ok, _profile3} =
Profiles.create_profile(%{
name: "Disabled",
vendor: "Ubiquiti",
priority: 50,
enabled: false
})
profiles = Profiles.list_profiles()
assert length(profiles) == 2
assert hd(profiles).id == profile1.id
assert Enum.at(profiles, 1).id == profile2.id
end
test "returns empty list when no enabled profiles" do
{:ok, _profile} =
Profiles.create_profile(%{
name: "Disabled",
vendor: "Test",
enabled: false
})
assert Profiles.list_profiles() == []
end
end
describe "get_profile/1" do
test "returns profile by name with associations" do
{:ok, profile} =
Profiles.create_profile(%{
name: "Test Profile",
vendor: "Test Vendor"
})
{:ok, _device_oid} =
Profiles.add_device_oid(profile.id, "serial_number", "TEST-MIB::serialNumber.0")
{:ok, _sensor_oid} =
Profiles.add_sensor_oid(profile.id, %{
sensor_type: "temperature",
mib_name: "TEST-MIB::temperature.0"
})
retrieved = Profiles.get_profile("Test Profile")
assert retrieved.id == profile.id
assert length(retrieved.device_oids) == 1
assert length(retrieved.sensor_oids) == 1
end
test "returns nil when profile doesn't exist" do
assert Profiles.get_profile("Nonexistent") == nil
end
end
describe "match_profile/1" do
setup do
{:ok, regex_profile} =
Profiles.create_profile(%{
name: "Regex Match",
vendor: "Cisco",
detection_pattern: "Cisco IOS",
priority: 10
})
{:ok, oid_profile} =
Profiles.create_profile(%{
name: "OID Match",
vendor: "MikroTik",
detection_oid: "1.3.6.1.4.1.14988",
priority: 20
})
{:ok, both_profile} =
Profiles.create_profile(%{
name: "Both Match",
vendor: "Ubiquiti",
detection_pattern: "EdgeRouter",
detection_oid: "1.3.6.1.4.1.41112",
priority: 30
})
%{
regex_profile: regex_profile,
oid_profile: oid_profile,
both_profile: both_profile
}
end
test "matches by detection_pattern regex", %{regex_profile: profile} do
system_info = %{
sys_descr: "Cisco IOS Software, Version 15.2",
sys_object_id: "1.3.6.1.4.1.9"
}
assert Profiles.match_profile(system_info).id == profile.id
end
test "matches by detection_oid prefix", %{oid_profile: profile} do
system_info = %{
sys_descr: "Some description",
sys_object_id: "1.3.6.1.4.1.14988.1.1"
}
assert Profiles.match_profile(system_info).id == profile.id
end
test "matches higher priority profile first", %{regex_profile: profile} do
system_info = %{
sys_descr: "Cisco IOS Software",
sys_object_id: "1.3.6.1.4.1.14988"
}
# Both profiles match, but regex_profile has higher priority (10 < 20)
assert Profiles.match_profile(system_info).id == profile.id
end
test "returns nil when no profile matches" do
system_info = %{
sys_descr: "Unknown Device",
sys_object_id: "1.2.3.4.5"
}
assert Profiles.match_profile(system_info) == nil
end
test "handles missing sys_descr" do
{:ok, profile} =
Profiles.create_profile(%{
name: "OID Only",
vendor: "Test",
detection_oid: "1.2.3.4"
})
system_info = %{sys_object_id: "1.2.3.4.5"}
assert Profiles.match_profile(system_info).id == profile.id
end
test "handles missing sys_object_id" do
{:ok, profile} =
Profiles.create_profile(%{
name: "Regex Only",
vendor: "Test",
detection_pattern: "Test Device"
})
system_info = %{sys_descr: "Test Device v1.0"}
assert Profiles.match_profile(system_info).id == profile.id
end
test "handles invalid regex pattern gracefully" do
{:ok, _profile} =
Profiles.create_profile(%{
name: "Invalid Regex",
vendor: "Test",
detection_pattern: "[invalid(regex"
})
system_info = %{sys_descr: "Some description"}
# Should not crash, just not match
assert Profiles.match_profile(system_info) == nil
end
end
describe "create_profile/1" do
test "creates profile with valid attributes" do
attrs = %{
name: "New Profile",
vendor: "Test Vendor",
detection_pattern: "Test.*Device",
detection_oid: "1.3.6.1.4.1.12345",
priority: 50,
enabled: true
}
assert {:ok, %DeviceProfile{} = profile} = Profiles.create_profile(attrs)
assert profile.name == "New Profile"
assert profile.vendor == "Test Vendor"
assert profile.detection_pattern == "Test.*Device"
assert profile.detection_oid == "1.3.6.1.4.1.12345"
assert profile.priority == 50
assert profile.enabled == true
end
test "creates profile with minimal attributes" do
attrs = %{
name: "Minimal Profile",
vendor: "Test"
}
assert {:ok, %DeviceProfile{} = profile} = Profiles.create_profile(attrs)
assert profile.name == "Minimal Profile"
assert profile.vendor == "Test"
assert profile.priority == 100
assert profile.enabled == true
end
test "returns error with missing required fields" do
assert {:error, changeset} = Profiles.create_profile(%{})
assert "can't be blank" in errors_on(changeset).name
assert "can't be blank" in errors_on(changeset).vendor
end
test "returns error with duplicate name" do
{:ok, _profile} = Profiles.create_profile(%{name: "Duplicate", vendor: "Test"})
assert {:error, changeset} = Profiles.create_profile(%{name: "Duplicate", vendor: "Test"})
assert "has already been taken" in errors_on(changeset).name
end
end
describe "update_profile/2" do
test "updates profile with valid attributes" do
{:ok, profile} = Profiles.create_profile(%{name: "Original", vendor: "Original Vendor"})
assert {:ok, updated} =
Profiles.update_profile(profile, %{
name: "Updated",
vendor: "Updated Vendor",
priority: 25
})
assert updated.name == "Updated"
assert updated.vendor == "Updated Vendor"
assert updated.priority == 25
end
test "returns error with invalid attributes" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
assert {:error, changeset} = Profiles.update_profile(profile, %{name: nil})
assert "can't be blank" in errors_on(changeset).name
end
end
describe "delete_profile/1" do
test "deletes profile" do
{:ok, profile} = Profiles.create_profile(%{name: "To Delete", vendor: "Test"})
assert {:ok, %DeviceProfile{}} = Profiles.delete_profile(profile)
assert Profiles.get_profile("To Delete") == nil
end
end
describe "add_device_oid/3" do
test "adds device OID to profile" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
assert {:ok, device_oid} =
Profiles.add_device_oid(
profile.id,
"serial_number",
"TEST-MIB::serialNumber.0"
)
assert device_oid.field == "serial_number"
assert device_oid.mib_name == "TEST-MIB::serialNumber.0"
assert device_oid.profile_id == profile.id
end
test "returns error with duplicate field for same profile" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
{:ok, _oid1} = Profiles.add_device_oid(profile.id, "serial_number", "MIB1::sn.0")
assert {:error, changeset} =
Profiles.add_device_oid(profile.id, "serial_number", "MIB2::sn.0")
assert "has already been taken" in errors_on(changeset).profile_id
end
end
describe "add_sensor_oid/2" do
test "adds sensor OID to profile" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
attrs = %{
sensor_type: "temperature",
mib_name: "TEST-MIB::temperature.0",
sensor_descr: "CPU Temperature",
sensor_unit: "celsius",
sensor_divisor: 10
}
assert {:ok, sensor_oid} = Profiles.add_sensor_oid(profile.id, attrs)
assert sensor_oid.sensor_type == "temperature"
assert sensor_oid.mib_name == "TEST-MIB::temperature.0"
assert sensor_oid.sensor_descr == "CPU Temperature"
assert sensor_oid.sensor_unit == "celsius"
assert sensor_oid.sensor_divisor == 10
assert sensor_oid.profile_id == profile.id
end
test "adds sensor OID with minimal attributes" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
attrs = %{
sensor_type: "voltage",
mib_name: "TEST-MIB::voltage.0"
}
assert {:ok, sensor_oid} = Profiles.add_sensor_oid(profile.id, attrs)
assert sensor_oid.sensor_type == "voltage"
assert sensor_oid.mib_name == "TEST-MIB::voltage.0"
assert sensor_oid.sensor_divisor == 1
end
test "returns error with missing required fields" do
{:ok, profile} = Profiles.create_profile(%{name: "Test", vendor: "Test"})
assert {:error, changeset} = Profiles.add_sensor_oid(profile.id, %{})
assert "can't be blank" in errors_on(changeset).sensor_type
assert "can't be blank" in errors_on(changeset).mib_name
end
end
end