test: expand coverage for devices, workers, and LiveView modules
Add tests for untested Devices context functions (get_device_by_ip, get_mikrotik_config, get_snmpv3_config, credential propagation, resolve_snmp_community, list_mikrotik_devices_with_api), activity log action descriptions, firmware version extraction edge cases, and discovery worker enqueue/retryable_error paths.
This commit is contained in:
parent
1c0cccdf25
commit
9f8b925ecd
6 changed files with 926 additions and 0 deletions
|
|
@ -1752,4 +1752,604 @@ defmodule Towerops.EquipmentTest do
|
|||
refute_receive {:device_status_changed, _}
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_device_by_ip/3" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "returns device matching IP and site", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
found = Devices.get_device_by_ip("192.168.1.1", site.id)
|
||||
assert found.id == device.id
|
||||
end
|
||||
|
||||
test "returns nil when IP not found in site", %{site: site} do
|
||||
assert Devices.get_device_by_ip("10.0.0.1", site.id) == nil
|
||||
end
|
||||
|
||||
test "returns nil when site_id is nil" do
|
||||
assert Devices.get_device_by_ip("192.168.1.1", nil) == nil
|
||||
end
|
||||
|
||||
test "excludes device with given exclude_id", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
assert Devices.get_device_by_ip("192.168.1.1", site.id, device.id) == nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_mikrotik_config/1" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "returns mikrotik config from device", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "MikroTik Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
mikrotik_enabled: true,
|
||||
mikrotik_username: "admin",
|
||||
mikrotik_port: 8729,
|
||||
mikrotik_use_ssl: true
|
||||
})
|
||||
|
||||
config = Devices.get_mikrotik_config(device)
|
||||
assert config.username == "admin"
|
||||
assert config.port == 8729
|
||||
assert config.use_ssl == true
|
||||
assert config.enabled == true
|
||||
end
|
||||
|
||||
test "returns defaults when fields are nil", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
config = Devices.get_mikrotik_config(device)
|
||||
assert config.port == 8729
|
||||
assert config.ssh_port == 22
|
||||
assert config.use_ssl == true
|
||||
assert config.enabled == false
|
||||
end
|
||||
|
||||
test "accepts device_id string", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
mikrotik_enabled: true,
|
||||
mikrotik_username: "admin"
|
||||
})
|
||||
|
||||
config = Devices.get_mikrotik_config(device.id)
|
||||
assert config.username == "admin"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get_snmpv3_config/1" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "returns nil for non-v3 device", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_version: "2c"
|
||||
})
|
||||
|
||||
assert Devices.get_snmpv3_config(device) == nil
|
||||
end
|
||||
|
||||
test "returns v3 config for v3 device", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_version: "3",
|
||||
snmpv3_security_level: "authPriv",
|
||||
snmpv3_username: "snmpuser",
|
||||
snmpv3_auth_protocol: "SHA-256",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_protocol: "AES",
|
||||
snmpv3_priv_password: "privpass123"
|
||||
})
|
||||
|
||||
config = Devices.get_snmpv3_config(device)
|
||||
assert config.security_level == "authPriv"
|
||||
assert config.username == "snmpuser"
|
||||
assert config.auth_protocol == "SHA-256"
|
||||
assert config.priv_protocol == "AES"
|
||||
end
|
||||
|
||||
test "accepts device_id string", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_version: "3",
|
||||
snmpv3_username: "snmpuser",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_password: "privpass123"
|
||||
})
|
||||
|
||||
config = Devices.get_snmpv3_config(device.id)
|
||||
assert config.username == "snmpuser"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_site_community_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Set community on site so devices will inherit from it
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id,
|
||||
snmp_community: "site-community"
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting from site", %{organization: organization, site: site} do
|
||||
# Device created without community → inherits from site, source = "site"
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: nil
|
||||
})
|
||||
|
||||
assert device.snmp_community_source == "site"
|
||||
|
||||
assert :ok = Devices.propagate_site_community_change(site.id, "new-community")
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmp_community == "new-community"
|
||||
end
|
||||
|
||||
test "does not update devices with device-level community", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: "device-community"
|
||||
})
|
||||
|
||||
assert device.snmp_community_source == "device"
|
||||
|
||||
assert :ok = Devices.propagate_site_community_change(site.id, "new-community")
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmp_community == "device-community"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_organization_community_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
|
||||
{:ok, organization} =
|
||||
Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Update org with community so devices can inherit from it
|
||||
{:ok, organization} =
|
||||
Towerops.Organizations.update_organization(organization, %{snmp_community: "org-community"})
|
||||
|
||||
# Site without community so devices inherit from org
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting from organization", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: nil
|
||||
})
|
||||
|
||||
assert device.snmp_community_source == "organization"
|
||||
|
||||
assert :ok = Devices.propagate_organization_community_change(organization.id, "new-community")
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmp_community == "new-community"
|
||||
end
|
||||
|
||||
test "does not update devices with device-level source", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: "device-community"
|
||||
})
|
||||
|
||||
assert device.snmp_community_source == "device"
|
||||
|
||||
assert :ok = Devices.propagate_organization_community_change(organization.id, "new-community")
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmp_community == "device-community"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_site_mikrotik_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Set mikrotik creds on site so devices inherit
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id,
|
||||
mikrotik_username: "site-user"
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting mikrotik from site", %{organization: organization, site: site} do
|
||||
# Device without mikrotik_username → inherits from site
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
assert device.mikrotik_credential_source == "site"
|
||||
|
||||
assert :ok =
|
||||
Devices.propagate_site_mikrotik_change(site.id, %{
|
||||
mikrotik_username: "new-user"
|
||||
})
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.mikrotik_username == "new-user"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_organization_mikrotik_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Set mikrotik creds on org so devices inherit
|
||||
{:ok, organization} =
|
||||
Towerops.Organizations.update_organization(organization, %{mikrotik_username: "org-user"})
|
||||
|
||||
# Site without mikrotik creds so devices inherit from org
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting mikrotik from organization", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
assert device.mikrotik_credential_source == "organization"
|
||||
|
||||
assert :ok =
|
||||
Devices.propagate_organization_mikrotik_change(organization.id, %{
|
||||
mikrotik_username: "new-user"
|
||||
})
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.mikrotik_username == "new-user"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_site_snmpv3_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Set snmpv3 creds on site
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id,
|
||||
snmpv3_username: "site-v3-user",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_password: "privpass123"
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting snmpv3 from site", %{organization: organization, site: site} do
|
||||
# Create device and manually set snmpv3_credential_source to "site"
|
||||
# (can't use create_device with v3 + no username due to changeset validation order)
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmpv3_username: "site-v3-user",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_password: "privpass123",
|
||||
snmp_version: "3"
|
||||
})
|
||||
|
||||
# Force source to "site" for propagation test
|
||||
device
|
||||
|> Ecto.Changeset.change(%{snmpv3_credential_source: "site"})
|
||||
|> Towerops.Repo.update!()
|
||||
|
||||
assert :ok =
|
||||
Devices.propagate_site_snmpv3_change(site.id, %{
|
||||
snmpv3_username: "new-v3-user"
|
||||
})
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmpv3_username == "new-v3-user"
|
||||
end
|
||||
end
|
||||
|
||||
describe "propagate_organization_snmpv3_change/2" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
|
||||
|
||||
# Set snmpv3 creds on org (with passwords required for v3 validation)
|
||||
{:ok, organization} =
|
||||
Towerops.Organizations.update_organization(organization, %{
|
||||
snmpv3_username: "org-v3-user",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_password: "privpass123"
|
||||
})
|
||||
|
||||
# Site without snmpv3 creds
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Test Site",
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "updates devices inheriting snmpv3 from organization", %{organization: organization, site: site} do
|
||||
# Create device with v3 credentials and force source to "organization"
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmpv3_username: "org-v3-user",
|
||||
snmpv3_auth_password: "authpass123",
|
||||
snmpv3_priv_password: "privpass123",
|
||||
snmp_version: "3"
|
||||
})
|
||||
|
||||
# Force source to "organization" for propagation test
|
||||
device
|
||||
|> Ecto.Changeset.change(%{snmpv3_credential_source: "organization"})
|
||||
|> Towerops.Repo.update!()
|
||||
|
||||
assert :ok =
|
||||
Devices.propagate_organization_snmpv3_change(organization.id, %{
|
||||
snmpv3_username: "new-v3-user"
|
||||
})
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.snmpv3_username == "new-v3-user"
|
||||
end
|
||||
end
|
||||
|
||||
describe "resolve_snmp_community/1" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "returns device community when source is device", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: "my-community"
|
||||
})
|
||||
|
||||
assert Devices.resolve_snmp_community(device) == "my-community"
|
||||
end
|
||||
|
||||
test "returns site community when device has none", %{organization: organization, site: site} do
|
||||
{:ok, site} = Towerops.Sites.update_site(site, %{snmp_community: "site-community"})
|
||||
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: nil,
|
||||
snmp_community_source: "site"
|
||||
})
|
||||
|
||||
assert Devices.resolve_snmp_community(device) == "site-community"
|
||||
end
|
||||
|
||||
test "returns org community when device and site have none", %{organization: organization, site: site} do
|
||||
{:ok, _org} =
|
||||
Towerops.Organizations.update_organization(organization, %{snmp_community: "org-community"})
|
||||
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
snmp_community: nil,
|
||||
snmp_community_source: "organization"
|
||||
})
|
||||
|
||||
assert Devices.resolve_snmp_community(device) == "org-community"
|
||||
end
|
||||
end
|
||||
|
||||
describe "list_mikrotik_devices_with_api/0" do
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
%{organization: organization, site: site}
|
||||
end
|
||||
|
||||
test "returns devices with mikrotik enabled and username", %{organization: organization, site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "MikroTik Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
mikrotik_enabled: true,
|
||||
mikrotik_username: "admin"
|
||||
})
|
||||
|
||||
result = Devices.list_mikrotik_devices_with_api()
|
||||
device_ids = Enum.map(result, & &1.id)
|
||||
assert device.id in device_ids
|
||||
end
|
||||
|
||||
test "excludes devices without mikrotik enabled", %{organization: organization, site: site} do
|
||||
{:ok, _device} =
|
||||
Devices.create_device(%{
|
||||
name: "Regular Router",
|
||||
ip_address: "192.168.1.1",
|
||||
site_id: site.id,
|
||||
organization_id: organization.id,
|
||||
mikrotik_enabled: false
|
||||
})
|
||||
|
||||
result = Devices.list_mikrotik_devices_with_api()
|
||||
assert result == []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ defmodule Towerops.Workers.DeviceMonitorWorkerTest do
|
|||
import Towerops.AccountsFixtures
|
||||
|
||||
alias Towerops.Devices
|
||||
alias Towerops.Monitoring.PingMock
|
||||
alias Towerops.Organizations
|
||||
alias Towerops.Sites
|
||||
alias Towerops.Workers.DeviceMonitorWorker
|
||||
|
|
@ -87,4 +88,78 @@ defmodule Towerops.Workers.DeviceMonitorWorkerTest do
|
|||
refute cancelled_jobs == []
|
||||
end
|
||||
end
|
||||
|
||||
describe "perform/1" do
|
||||
test "skips check for deleted device" do
|
||||
fake_id = Ecto.UUID.generate()
|
||||
assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => fake_id}})
|
||||
end
|
||||
|
||||
test "performs check and reschedules for monitoring-enabled device", %{site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Monitor Me",
|
||||
ip_address: "192.168.1.10",
|
||||
site_id: site.id,
|
||||
organization_id: site.organization_id,
|
||||
monitoring_enabled: true
|
||||
})
|
||||
|
||||
Mox.expect(PingMock, :ping, fn _ip -> {:ok, 5.0} end)
|
||||
|
||||
assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})
|
||||
|
||||
# Device should be marked up
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.status == :up
|
||||
end
|
||||
|
||||
test "handles ping failure and marks device down", %{site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Failing Device",
|
||||
ip_address: "192.168.1.11",
|
||||
site_id: site.id,
|
||||
organization_id: site.organization_id,
|
||||
monitoring_enabled: true
|
||||
})
|
||||
|
||||
Mox.expect(PingMock, :ping, fn _ip -> {:error, :timeout} end)
|
||||
|
||||
assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})
|
||||
|
||||
updated = Devices.get_device!(device.id)
|
||||
assert updated.status == :down
|
||||
end
|
||||
|
||||
test "does not check when monitoring is disabled", %{site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "No Monitor",
|
||||
ip_address: "192.168.1.12",
|
||||
site_id: site.id,
|
||||
organization_id: site.organization_id,
|
||||
monitoring_enabled: false
|
||||
})
|
||||
|
||||
# No mock expectation - ping should not be called
|
||||
assert :ok == DeviceMonitorWorker.perform(%Oban.Job{args: %{"device_id" => device.id}})
|
||||
end
|
||||
end
|
||||
|
||||
describe "trigger_check/1" do
|
||||
test "inserts an immediate job", %{site: site} do
|
||||
{:ok, device} =
|
||||
Devices.create_device(%{
|
||||
name: "Trigger Me",
|
||||
ip_address: "192.168.1.13",
|
||||
site_id: site.id,
|
||||
organization_id: site.organization_id,
|
||||
monitoring_enabled: true
|
||||
})
|
||||
|
||||
assert {:ok, job} = DeviceMonitorWorker.trigger_check(device.id)
|
||||
assert job.args["device_id"] == device.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -132,6 +132,39 @@ defmodule Towerops.Workers.DiscoveryWorkerTest do
|
|||
end
|
||||
end
|
||||
|
||||
describe "enqueue/1" do
|
||||
setup do
|
||||
user = user_fixture()
|
||||
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Enqueue Org"}, user.id)
|
||||
|
||||
{:ok, site} =
|
||||
Towerops.Sites.create_site(%{
|
||||
name: "Enqueue Site",
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
{:ok, device} =
|
||||
Towerops.Devices.create_device(%{
|
||||
name: "Enqueue Device",
|
||||
ip_address: "192.168.1.50",
|
||||
snmp_enabled: true,
|
||||
site_id: site.id,
|
||||
organization_id: organization.id
|
||||
})
|
||||
|
||||
{:ok, device: device}
|
||||
end
|
||||
|
||||
test "enqueues a discovery job", %{device: device} do
|
||||
assert :ok = DiscoveryWorker.enqueue(device.id)
|
||||
end
|
||||
|
||||
test "deduplicates jobs within 60 seconds", %{device: device} do
|
||||
assert :ok = DiscoveryWorker.enqueue(device.id)
|
||||
assert :ok = DiscoveryWorker.enqueue(device.id)
|
||||
end
|
||||
end
|
||||
|
||||
describe "retryable_error?/1" do
|
||||
test "timeout is retryable" do
|
||||
assert DiscoveryWorker.retryable_error?(:timeout)
|
||||
|
|
@ -157,6 +190,14 @@ defmodule Towerops.Workers.DiscoveryWorkerTest do
|
|||
refute DiscoveryWorker.retryable_error?(:invalid_config)
|
||||
end
|
||||
|
||||
test "no_snmp_credentials is not retryable" do
|
||||
refute DiscoveryWorker.retryable_error?(:no_snmp_credentials)
|
||||
end
|
||||
|
||||
test "device_deleted is not retryable" do
|
||||
refute DiscoveryWorker.retryable_error?(:device_deleted)
|
||||
end
|
||||
|
||||
test "unknown errors are retryable by default" do
|
||||
assert DiscoveryWorker.retryable_error?(:some_unknown_error)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -128,6 +128,67 @@ defmodule Towerops.Workers.FirmwareVersionFetcherWorkerTest do
|
|||
assert is_nil(data.release_date)
|
||||
end
|
||||
|
||||
test "extracts version number from 'RouterOS X.Y.Z [stable]' format" do
|
||||
rss_with_full_version = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>RouterOS latest stable version</title>
|
||||
<item>
|
||||
<title>RouterOS 7.21.2 [stable]</title>
|
||||
<link>https://mikrotik.com/download</link>
|
||||
<pubDate>Wed, 15 Jan 2025 12:00:00 +0000</pubDate>
|
||||
<description>RouterOS 7.21.2 stable release</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
assert {:ok, data} = FirmwareVersionFetcherWorker.parse_rss_feed(rss_with_full_version)
|
||||
assert data.version == "7.21.2"
|
||||
assert data.metadata["raw_version"] == "RouterOS 7.21.2 [stable]"
|
||||
end
|
||||
|
||||
test "extracts two-part version number" do
|
||||
rss_with_two_part = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>RouterOS latest stable version</title>
|
||||
<item>
|
||||
<title>RouterOS 7.21 [stable]</title>
|
||||
<link>https://mikrotik.com/download</link>
|
||||
<pubDate>Wed, 15 Jan 2025 12:00:00 +0000</pubDate>
|
||||
<description>Test</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
assert {:ok, data} = FirmwareVersionFetcherWorker.parse_rss_feed(rss_with_two_part)
|
||||
assert data.version == "7.21"
|
||||
end
|
||||
|
||||
test "handles missing pubDate" do
|
||||
rss_no_date = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<rss version="2.0">
|
||||
<channel>
|
||||
<title>RouterOS latest stable version</title>
|
||||
<item>
|
||||
<title>7.14.1</title>
|
||||
<link>https://mikrotik.com/download</link>
|
||||
<description>Test</description>
|
||||
</item>
|
||||
</channel>
|
||||
</rss>
|
||||
"""
|
||||
|
||||
assert {:ok, data} = FirmwareVersionFetcherWorker.parse_rss_feed(rss_no_date)
|
||||
assert data.version == "7.14.1"
|
||||
assert is_nil(data.release_date)
|
||||
end
|
||||
|
||||
test "stores metadata correctly" do
|
||||
rss_with_metadata = """
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ defmodule ToweropsWeb.UserResetPasswordControllerTest do
|
|||
|
||||
import Towerops.AccountsFixtures
|
||||
|
||||
# Note: edit/update routes are handled by UserResetPasswordLive, not this controller
|
||||
|
||||
setup do
|
||||
%{user: user_fixture(enable_totp: false)}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -26,5 +26,152 @@ defmodule ToweropsWeb.AccountLive.ActivityTest do
|
|||
|
||||
assert html =~ "User data viewed"
|
||||
end
|
||||
|
||||
test "shows sudo_mode_granted from setup", %{conn: conn} do
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
|
||||
assert html =~ "Sudo mode granted"
|
||||
end
|
||||
end
|
||||
|
||||
describe "action descriptions" do
|
||||
test "displays user_data_exported action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "user_data_exported",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "exported your account data"
|
||||
end
|
||||
|
||||
test "displays user_profile_updated action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "user_profile_updated",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "updated your profile"
|
||||
end
|
||||
|
||||
test "displays device_created action with metadata", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "device_created",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"device_name" => "Test Router"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "Test Router"
|
||||
end
|
||||
|
||||
test "displays device_deleted action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "device_deleted",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"device_name" => "Old Router"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "Old Router"
|
||||
end
|
||||
|
||||
test "displays device_updated action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "device_updated",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"device_id" => "abc-123"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "abc-123"
|
||||
end
|
||||
|
||||
test "displays failed_access_attempt action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "failed_access_attempt",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"resource" => "/admin/secret"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "/admin/secret"
|
||||
end
|
||||
|
||||
test "displays privilege_escalation action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "privilege_escalation",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"from_role" => "member", "to_role" => "admin"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "member"
|
||||
assert html =~ "admin"
|
||||
end
|
||||
|
||||
test "displays impersonate_start action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "impersonate_start",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "impersonating"
|
||||
end
|
||||
|
||||
test "displays impersonate_end action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "impersonate_end",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "stopped impersonating"
|
||||
end
|
||||
|
||||
test "displays org_data_accessed action", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "org_data_accessed",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id,
|
||||
metadata: %{"organization_id" => "org-123", "action" => "exported"}
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "org-123"
|
||||
end
|
||||
|
||||
test "displays fallback for action without handler", %{conn: conn, user: user} do
|
||||
{:ok, _} =
|
||||
Admin.create_audit_log(%{
|
||||
action: "monitoring_data_queried",
|
||||
superuser_id: user.id,
|
||||
target_user_id: user.id
|
||||
})
|
||||
|
||||
{:ok, _view, html} = live(conn, ~p"/account/activity")
|
||||
assert html =~ "Monitoring data queried"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue