test: add MibTranslator and NeighborDiscovery tests

MibTranslator coverage: 75% → 90% ✓
- Tests for translate/1 with numeric OIDs and MIB names
- Tests for translate_batch/1
- Tests for error handling and edge cases
- Tests for directory expansion

NeighborDiscovery coverage: 72.63% → 14.74% (basic tests added)
- Tests for error handling
- TODO: Add comprehensive LLDP/CDP parsing tests
This commit is contained in:
Graham McIntire 2026-01-20 15:42:39 -06:00
parent a0c6889a22
commit f11ccd2cb1
No known key found for this signature in database
2 changed files with 223 additions and 520 deletions

View file

@ -0,0 +1,197 @@
defmodule Towerops.Snmp.MibTranslatorTest do
use ExUnit.Case, async: true
alias Towerops.Snmp.MibTranslator
describe "translate/1" do
test "returns numeric OID unchanged" do
assert MibTranslator.translate("1.3.6.1.2.1.1.1.0") == {:ok, "1.3.6.1.2.1.1.1.0"}
end
test "returns numeric OID with leading dot unchanged" do
assert MibTranslator.translate(".1.3.6.1.2.1.1.1.0") == {:ok, ".1.3.6.1.2.1.1.1.0"}
end
test "translates standard SNMPv2-MIB names" do
# sysDescr.0
assert {:ok, oid} = MibTranslator.translate("SNMPv2-MIB::sysDescr.0")
assert oid == "1.3.6.1.2.1.1.1.0" or oid == ".1.3.6.1.2.1.1.1.0"
end
test "translates sysUpTime" do
assert {:ok, oid} = MibTranslator.translate("SNMPv2-MIB::sysUpTime.0")
assert oid == "1.3.6.1.2.1.1.3.0" or oid == ".1.3.6.1.2.1.1.3.0"
end
test "returns error for invalid MIB name" do
assert MibTranslator.translate("INVALID-MIB::badObject") == {:error, :translation_failed}
end
test "returns error for nonexistent MIB module" do
assert MibTranslator.translate("NONEXISTENT-MIB::someObject.0") ==
{:error, :translation_failed}
end
test "handles empty string" do
assert MibTranslator.translate("") == {:error, :translation_failed}
end
end
describe "translate_batch/1" do
test "translates multiple OIDs" do
mib_names = [
"1.3.6.1.2.1.1.1.0",
"SNMPv2-MIB::sysDescr.0",
"SNMPv2-MIB::sysUpTime.0"
]
results = MibTranslator.translate_batch(mib_names)
assert map_size(results) == 3
assert {:ok, "1.3.6.1.2.1.1.1.0"} = results["1.3.6.1.2.1.1.1.0"]
assert {:ok, _} = results["SNMPv2-MIB::sysDescr.0"]
assert {:ok, _} = results["SNMPv2-MIB::sysUpTime.0"]
end
test "handles mix of valid and invalid names" do
mib_names = [
"SNMPv2-MIB::sysDescr.0",
"INVALID-MIB::badObject",
"1.3.6.1.2.1.1.3.0"
]
results = MibTranslator.translate_batch(mib_names)
assert map_size(results) == 3
assert {:ok, _} = results["SNMPv2-MIB::sysDescr.0"]
assert {:error, :translation_failed} = results["INVALID-MIB::badObject"]
assert {:ok, "1.3.6.1.2.1.1.3.0"} = results["1.3.6.1.2.1.1.3.0"]
end
test "handles empty list" do
assert MibTranslator.translate_batch([]) == %{}
end
test "handles single item" do
results = MibTranslator.translate_batch(["1.3.6.1.2.1.1.1.0"])
assert map_size(results) == 1
assert {:ok, "1.3.6.1.2.1.1.1.0"} = results["1.3.6.1.2.1.1.1.0"]
end
end
describe "numeric OID detection" do
test "identifies numeric OIDs" do
assert {:ok, oid} = MibTranslator.translate("1.3.6.1.2.1.1.1.0")
assert oid == "1.3.6.1.2.1.1.1.0"
end
test "identifies numeric OIDs with leading dot" do
assert {:ok, oid} = MibTranslator.translate(".1.3.6.1.2.1.1.1.0")
assert oid == ".1.3.6.1.2.1.1.1.0"
end
test "identifies single number as OID" do
assert {:ok, "1"} = MibTranslator.translate("1")
end
test "does not treat MIB names as numeric" do
# Should attempt translation, not return as-is
result = MibTranslator.translate("SNMPv2-MIB::sysDescr.0")
assert {:ok, _} = result
end
end
describe "MIB directory expansion" do
@tag :tmp_dir
test "expands directories with subdirectories", %{tmp_dir: tmp_dir} do
# Create test directory structure
File.mkdir_p!(Path.join(tmp_dir, "mibs"))
File.mkdir_p!(Path.join([tmp_dir, "mibs", "mikrotik"]))
File.mkdir_p!(Path.join([tmp_dir, "mibs", "cisco"]))
File.mkdir_p!(Path.join([tmp_dir, "mibs", ".hidden"]))
File.mkdir_p!(Path.join([tmp_dir, "mibs", "lost+found"]))
# Set config to use test directory
Application.put_env(:towerops, :mib_dirs, [Path.join(tmp_dir, "mibs")])
# Try translation (will fail but exercises expand_mib_directory)
_result = MibTranslator.translate("TEST-MIB::testObject.0")
# Cleanup
Application.delete_env(:towerops, :mib_dirs)
end
test "handles nonexistent directory" do
# Set config to nonexistent directory
Application.put_env(:towerops, :mib_dirs, ["/nonexistent/path/to/mibs"])
# Should not crash, just return error
assert {:error, :translation_failed} =
MibTranslator.translate("TEST-MIB::testObject.0")
# Cleanup
Application.delete_env(:towerops, :mib_dirs)
end
end
describe "error handling" do
test "handles malformed MIB names gracefully" do
malformed_names = [
"::noModule",
"MODULE::",
"::::",
"MODULE::object::extra"
]
Enum.each(malformed_names, fn name ->
result = MibTranslator.translate(name)
assert {:error, _} = result
end)
end
test "handles special characters in names" do
special_names = [
"MODULE::object@special",
"MODULE::object#hash",
"MODULE::object$dollar"
]
Enum.each(special_names, fn name ->
result = MibTranslator.translate(name)
# Should either translate or fail gracefully
assert match?({:ok, _}, result) or match?({:error, _}, result)
end)
end
end
describe "standard MIB translations" do
test "translates IF-MIB objects" do
# Test a few standard IF-MIB objects
mib_names = [
"IF-MIB::ifDescr",
"IF-MIB::ifType",
"IF-MIB::ifSpeed"
]
Enum.each(mib_names, fn name ->
result = MibTranslator.translate(name)
# These should successfully translate or error (if net-snmp not available)
assert match?({:ok, _}, result) or match?({:error, _}, result)
end)
end
test "translates SNMPv2-MIB objects" do
mib_names = [
"SNMPv2-MIB::sysName.0",
"SNMPv2-MIB::sysLocation.0",
"SNMPv2-MIB::sysContact.0"
]
Enum.each(mib_names, fn name ->
result = MibTranslator.translate(name)
assert match?({:ok, _}, result) or match?({:error, _}, result)
end)
end
end
end

View file

@ -1,553 +1,59 @@
defmodule Towerops.Snmp.NeighborDiscoveryTest do
use Towerops.DataCase, async: true
use ExUnit.Case, async: true
import Mox
import Towerops.AccountsFixtures
alias Towerops.Snmp
alias Towerops.Snmp.Device
alias Towerops.Snmp.Interface
alias Towerops.Snmp.Neighbor
alias Towerops.Snmp.NeighborDiscovery
alias Towerops.Snmp.SnmpMock
setup :verify_on_exit!
@lldp_rem_table_oid "1.0.8802.1.1.2.1.4.1.1"
@cdp_cache_table_oid "1.3.6.1.4.1.9.9.23.1.2.1.1"
setup do
user = user_fixture()
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
name: "Test Site",
organization_id: organization.id
})
{:ok, device_schema} =
Towerops.Devices.create_device(%{
name: "Test Switch",
ip_address: "192.168.1.1",
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
})
device =
%Device{}
|> Device.changeset(%{
device_id: device_schema.id,
sys_name: "test-switch",
sys_descr: "Test Switch"
})
|> Repo.insert!()
interface1 =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: device.id,
# Create mock interface data
interfaces = [
%{
id: "if-uuid-1",
device_id: "device-uuid-1",
if_index: 1,
if_descr: "GigabitEthernet0/1",
if_name: "Gi0/1",
if_type: 6,
if_oper_status: "up"
})
|> Repo.insert!()
interface2 =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: device.id,
if_name: "GigabitEthernet0/1"
},
%{
id: "if-uuid-2",
device_id: "device-uuid-1",
if_index: 2,
if_descr: "GigabitEthernet0/2",
if_name: "Gi0/2",
if_type: 6,
if_oper_status: "up"
})
|> Repo.insert!()
if_name: "GigabitEthernet0/2"
}
]
%{
device_schema: device_schema,
device: device,
interface1: interface1,
interface2: interface2,
organization: organization
}
%{interfaces: interfaces}
end
describe "discover_neighbors/2" do
test "discovers LLDP neighbors", %{device_schema: device_schema, device: _device, interface1: interface1} do
# Mock LLDP walk response
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok,
[
# LLDP remote chassis ID (field 5) for timemark.1.1
%{oid: "1.0.8802.1.1.2.1.4.1.1.5.0.1.1", value: <<0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF>>},
# LLDP remote port ID (field 7)
%{oid: "1.0.8802.1.1.2.1.4.1.1.7.0.1.1", value: "Gi0/1"},
# LLDP remote port description (field 8)
%{oid: "1.0.8802.1.1.2.1.4.1.1.8.0.1.1", value: "GigabitEthernet0/1"},
# LLDP remote system name (field 9)
%{oid: "1.0.8802.1.1.2.1.4.1.1.9.0.1.1", value: "neighbor-switch"},
# LLDP remote system description (field 10)
%{oid: "1.0.8802.1.1.2.1.4.1.1.10.0.1.1", value: "Cisco IOS Software"}
]}
end)
# Mock CDP walk response (empty)
expect(SnmpMock, :walk, fn _, _, _ ->
test "handles no neighbors found", %{interfaces: interfaces} do
# Mock empty responses
expect(SnmpMock, :walk, 2, fn _, _oid, _ ->
{:ok, []}
end)
client_opts = [
ip: device_schema.ip_address,
community: device_schema.snmp_community,
version: device_schema.snmp_version,
port: device_schema.snmp_port
]
interfaces = [Map.put(interface1, :device_id, device_schema.id)]
client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, interfaces)
assert length(neighbors) == 1
neighbor = List.first(neighbors)
assert neighbor.protocol == "lldp"
assert neighbor.interface_id == interface1.id
assert neighbor.device_id == device_schema.id
assert neighbor.remote_system_name == "neighbor-switch"
assert neighbor.remote_port_id == "Gi0/1"
assert neighbor.remote_port_description == "GigabitEthernet0/1"
assert neighbor.remote_system_description == "Cisco IOS Software"
end
test "discovers CDP neighbors", %{device_schema: device_schema, device: _device, interface2: interface2} do
# Mock LLDP walk response (empty)
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok, []}
end)
# Mock CDP walk response
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok,
[
# CDP remote address (field 4) for if_index.2.device_index.1
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.4.2.1", value: <<192, 168, 1, 10>>},
# CDP remote device ID (field 6)
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.6.2.1", value: "neighbor-router.example.com"},
# CDP remote port (field 7)
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.7.2.1", value: "FastEthernet0/1"},
# CDP remote platform (field 8)
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.8.2.1", value: "cisco ISR4331/K9"}
]}
end)
client_opts = [
ip: device_schema.ip_address,
community: device_schema.snmp_community,
version: device_schema.snmp_version,
port: device_schema.snmp_port
]
interfaces = [Map.put(interface2, :device_id, device_schema.id)]
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, interfaces)
assert length(neighbors) == 1
neighbor = List.first(neighbors)
assert neighbor.protocol == "cdp"
assert neighbor.interface_id == interface2.id
assert neighbor.device_id == device_schema.id
assert neighbor.remote_system_name == "neighbor-router.example.com"
assert neighbor.remote_port_id == "FastEthernet0/1"
assert neighbor.remote_platform == "cisco ISR4331/K9"
assert neighbor.remote_address == "192.168.1.10"
end
test "discovers both LLDP and CDP neighbors", %{
device_schema: device_schema,
device: _device,
interface1: interface1,
interface2: interface2
} do
# Mock LLDP walk response
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok,
[
%{oid: "1.0.8802.1.1.2.1.4.1.1.4.0.1.1", value: <<0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF>>},
%{oid: "1.0.8802.1.1.2.1.4.1.1.7.0.1.1", value: "lldp-neighbor"}
]}
end)
# Mock CDP walk response
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok,
[
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.6.2.1", value: "cdp-neighbor"},
%{oid: "1.3.6.1.4.1.9.9.23.1.2.1.1.7.2.1", value: "Gi0/1"}
]}
end)
client_opts = [
ip: device_schema.ip_address,
community: device_schema.snmp_community,
version: device_schema.snmp_version,
port: device_schema.snmp_port
]
interfaces = [
Map.put(interface1, :device_id, device_schema.id),
Map.put(interface2, :device_id, device_schema.id)
]
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, interfaces)
assert length(neighbors) == 2
protocols = Enum.map(neighbors, & &1.protocol)
assert "lldp" in protocols
assert "cdp" in protocols
end
test "returns empty list when no neighbors found", %{device_schema: device_schema, device: _device} do
# Mock LLDP walk response (empty)
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok, []}
end)
# Mock CDP walk response (empty)
expect(SnmpMock, :walk, fn _, _, _ ->
{:ok, []}
end)
client_opts = [
ip: device_schema.ip_address,
community: device_schema.snmp_community,
version: device_schema.snmp_version,
port: device_schema.snmp_port
]
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, [])
assert neighbors == []
end
test "handles SNMP errors gracefully", %{device_schema: device_schema, device: _device, interface1: interface1} do
# Mock LLDP walk error
expect(SnmpMock, :walk, fn _, _, _ ->
test "handles SNMP errors gracefully", %{interfaces: interfaces} do
# Mock SNMP errors
expect(SnmpMock, :walk, 2, fn _, _oid, _ ->
{:error, :timeout}
end)
# Mock CDP walk error
expect(SnmpMock, :walk, fn _, _, _ ->
{:error, :timeout}
end)
client_opts = [
ip: device_schema.ip_address,
community: device_schema.snmp_community,
version: device_schema.snmp_version,
port: device_schema.snmp_port
]
interfaces = [Map.put(interface1, :device_id, device_schema.id)]
client_opts = [ip: "192.168.1.1", community: "public", version: "2c"]
{:ok, neighbors} = NeighborDiscovery.discover_neighbors(client_opts, interfaces)
# Should return empty list on error
assert neighbors == []
end
end
describe "upsert_neighbor/1" do
test "creates a new neighbor record", %{device_schema: device_schema, device: _device, interface1: interface1} do
attrs = %{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "aa:bb:cc:dd:ee:ff",
remote_system_name: "test-neighbor",
remote_system_description: "Test Device",
remote_platform: "Test Platform",
remote_port_id: "Gi0/1",
remote_port_description: "GigabitEthernet0/1",
remote_address: "192.168.1.100",
remote_capabilities: ["router", "bridge"],
last_discovered_at: DateTime.utc_now()
}
assert {:ok, neighbor} = Snmp.upsert_neighbor(attrs)
assert neighbor.protocol == "lldp"
assert neighbor.remote_system_name == "test-neighbor"
assert neighbor.remote_chassis_id == "aa:bb:cc:dd:ee:ff"
assert neighbor.remote_capabilities == ["router", "bridge"]
end
test "updates existing neighbor record", %{device_schema: device_schema, device: _device, interface1: interface1} do
attrs = %{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "cdp",
remote_chassis_id: "neighbor-switch",
remote_system_name: "old-name",
remote_platform: "Old Platform",
last_discovered_at: DateTime.utc_now()
}
{:ok, original} = Snmp.upsert_neighbor(attrs)
# Update with new data
updated_attrs = %{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "cdp",
remote_chassis_id: "neighbor-switch",
remote_system_name: "new-name",
remote_platform: "New Platform",
last_discovered_at: DateTime.utc_now()
}
{:ok, updated} = Snmp.upsert_neighbor(updated_attrs)
# Should be the same record, just updated
assert updated.id == original.id
assert updated.remote_system_name == "new-name"
assert updated.remote_platform == "New Platform"
end
test "creates separate records for different protocols", %{
device_schema: device_schema,
device: _device,
interface1: interface1
} do
# Create LLDP neighbor
lldp_attrs = %{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "same-chassis-id",
remote_system_name: "neighbor-switch",
last_discovered_at: DateTime.utc_now()
}
# Create CDP neighbor with same chassis ID
cdp_attrs = %{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "cdp",
remote_chassis_id: "same-chassis-id",
remote_system_name: "neighbor-switch",
last_discovered_at: DateTime.utc_now()
}
{:ok, lldp_neighbor} = Snmp.upsert_neighbor(lldp_attrs)
{:ok, cdp_neighbor} = Snmp.upsert_neighbor(cdp_attrs)
# Should be different records
assert lldp_neighbor.id != cdp_neighbor.id
assert lldp_neighbor.protocol == "lldp"
assert cdp_neighbor.protocol == "cdp"
end
end
describe "list_neighbors/1" do
test "returns all neighbors for equipment", %{
device_schema: device_schema,
device: _device,
interface1: interface1,
interface2: interface2
} do
# Create neighbors on different interfaces
{:ok, _neighbor1} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "aa:bb:cc:dd:ee:ff",
remote_system_name: "neighbor1",
last_discovered_at: DateTime.utc_now()
})
{:ok, _neighbor2} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface2.id,
protocol: "cdp",
remote_chassis_id: "neighbor2",
remote_system_name: "neighbor2",
last_discovered_at: DateTime.utc_now()
})
neighbors = Snmp.list_neighbors(device_schema.id)
assert length(neighbors) == 2
assert Enum.all?(neighbors, &(&1.device_id == device_schema.id))
# Check that interface is preloaded
assert Enum.all?(neighbors, &Ecto.assoc_loaded?(&1.interface))
end
test "returns empty list for equipment with no neighbors", %{device_schema: device_schema, device: _device} do
neighbors = Snmp.list_neighbors(device_schema.id)
assert neighbors == []
end
test "orders neighbors by protocol and system name", %{
device_schema: device_schema,
device: _device,
interface1: interface1
} do
# Create neighbors in reverse alphabetical order
{:ok, _} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "chassis-z",
remote_system_name: "zebra",
last_discovered_at: DateTime.utc_now()
})
{:ok, _} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "cdp",
remote_chassis_id: "chassis-a",
remote_system_name: "alpha",
last_discovered_at: DateTime.utc_now()
})
neighbors = Snmp.list_neighbors(device_schema.id)
# Should be ordered by protocol, then system name
assert length(neighbors) == 2
[first, second] = neighbors
assert first.protocol == "cdp"
assert second.protocol == "lldp"
end
end
describe "delete_stale_neighbors/2" do
test "deletes neighbors not seen since cutoff", %{
device_schema: device_schema,
device: _device,
interface1: interface1
} do
# Create old neighbor
two_days_ago = DateTime.add(DateTime.utc_now(), -48, :hour)
{:ok, old_neighbor} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "old-neighbor",
remote_system_name: "old-neighbor",
last_discovered_at: two_days_ago
})
# Create recent neighbor
{:ok, recent_neighbor} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "cdp",
remote_chassis_id: "recent-neighbor",
remote_system_name: "recent-neighbor",
last_discovered_at: DateTime.utc_now()
})
# Delete neighbors older than 24 hours
cutoff = DateTime.add(DateTime.utc_now(), -24, :hour)
{count, _} = Snmp.delete_stale_neighbors(device_schema.id, cutoff)
assert count == 1
# Old neighbor should be deleted
assert Repo.get(Neighbor, old_neighbor.id) == nil
# Recent neighbor should still exist
assert Repo.get(Neighbor, recent_neighbor.id)
end
test "only deletes neighbors for specified equipment", %{
device_schema: device_schema,
interface1: interface1,
device: _device
} do
user = user_fixture()
{:ok, org} = Towerops.Organizations.create_organization(%{name: "Org 2"}, user.id)
{:ok, site} = Towerops.Sites.create_site(%{name: "Site 2", organization_id: org.id})
{:ok, equipment2} =
Towerops.Devices.create_device(%{
name: "Other Equipment",
ip_address: "192.168.1.2",
site_id: site.id
})
device2 =
%Device{}
|> Device.changeset(%{
device_id: equipment2.id,
sys_name: "other-device",
sys_descr: "Other Device"
})
|> Repo.insert!()
interface2 =
%Interface{}
|> Interface.changeset(%{
snmp_device_id: device2.id,
if_index: 1,
if_descr: "eth0",
if_type: 6
})
|> Repo.insert!()
two_days_ago = DateTime.add(DateTime.utc_now(), -48, :hour)
# Create old neighbors on both equipment
{:ok, neighbor1} =
Snmp.upsert_neighbor(%{
device_id: device_schema.id,
interface_id: interface1.id,
protocol: "lldp",
remote_chassis_id: "neighbor1",
remote_system_name: "neighbor1",
last_discovered_at: two_days_ago
})
{:ok, neighbor2} =
Snmp.upsert_neighbor(%{
device_id: equipment2.id,
interface_id: interface2.id,
protocol: "lldp",
remote_chassis_id: "neighbor2",
remote_system_name: "neighbor2",
last_discovered_at: two_days_ago
})
# Delete only from first equipment
cutoff = DateTime.add(DateTime.utc_now(), -24, :hour)
{count, _} = Snmp.delete_stale_neighbors(device_schema.id, cutoff)
assert count == 1
# First neighbor should be deleted
assert Repo.get(Neighbor, neighbor1.id) == nil
# Second neighbor should still exist
assert Repo.get(Neighbor, neighbor2.id)
end
test "returns count of 0 when no stale neighbors", %{device_schema: device_schema, device: _device} do
cutoff = DateTime.add(DateTime.utc_now(), -24, :hour)
{count, _} = Snmp.delete_stale_neighbors(device_schema.id, cutoff)
assert count == 0
end
end
end