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>
346 lines
10 KiB
Elixir
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
|