make devices site optional and many test fix

This commit is contained in:
Graham McIntire 2026-02-04 15:05:42 -06:00
parent 1a054fd598
commit 8382b5df6a
No known key found for this signature in database
54 changed files with 833 additions and 260 deletions

View file

@ -51,7 +51,7 @@ defmodule Towerops.Devices.Device do
field :snmpv3_credential_source, :string, default: "site"
# MikroTik API credentials (device-level overrides)
field :mikrotik_username, Binary
field :mikrotik_username, :string
field :mikrotik_password, Binary
field :mikrotik_port, :integer
field :mikrotik_ssh_port, :integer

View file

@ -13,6 +13,7 @@ defmodule Towerops.Organizations do
alias Towerops.Organizations.Policy
alias Towerops.Organizations.SubscriptionLimits
alias Towerops.Repo
alias Towerops.Sites.Site
## Organizations
@ -102,11 +103,24 @@ defmodule Towerops.Organizations do
role: :owner
})
end)
|> Ecto.Multi.run(:set_default, fn _repo, %{organization: organization} ->
# Set this as the user's default organization if they don't have one
user = Towerops.Accounts.get_user!(user_id)
if user.default_organization_id == nil do
user
|> Ecto.Changeset.change(default_organization_id: organization.id)
|> Repo.update()
else
{:ok, user}
end
end)
case Repo.transaction(multi) do
{:ok, %{organization: organization}} -> {:ok, organization}
{:error, :organization, changeset, _} -> {:error, changeset}
{:error, :membership, changeset, _} -> {:error, changeset}
{:error, :set_default, changeset, _} -> {:error, changeset}
end
end
@ -415,6 +429,7 @@ defmodule Towerops.Organizations do
Returns the number of device records updated.
"""
def clear_all_site_assignments(organization_id) do
# Clear site assignments from all devices
Repo.update_all(
from(d in Device, where: d.organization_id == ^organization_id),
set: [
@ -422,6 +437,9 @@ defmodule Towerops.Organizations do
updated_at: DateTime.truncate(DateTime.utc_now(), :second)
]
)
# Delete all sites for the organization
Repo.delete_all(from(s in Site, where: s.organization_id == ^organization_id))
end
## Authorization

View file

@ -41,7 +41,7 @@ defmodule Towerops.Organizations.Organization do
field :snmpv3_priv_password, Binary
# MikroTik API credentials (organization-level defaults cascade to site → device)
field :mikrotik_username, Binary
field :mikrotik_username, :string
field :mikrotik_password, Binary
field :mikrotik_port, :integer, default: 8729
field :mikrotik_ssh_port, :integer, default: 22

View file

@ -37,7 +37,7 @@ defmodule Towerops.Sites.Site do
field :snmpv3_priv_password, Binary
# MikroTik API credentials (overrides organization default)
field :mikrotik_username, Binary
field :mikrotik_username, :string
field :mikrotik_password, Binary
field :mikrotik_port, :integer
field :mikrotik_ssh_port, :integer

View file

@ -98,8 +98,8 @@ defmodule ToweropsWeb.Api.AccountDataController do
snmp_enabled: device.snmp_enabled,
status: device.status,
organization: %{
id: device.site.organization_id,
name: device.site.organization.name
id: device.organization_id,
name: device.organization.name
},
site: %{
id: device.site_id,

View file

@ -47,20 +47,28 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
@doc """
POST /api/v1/devices
Creates a new device.
Creates a new device. The device can be assigned to a site or directly to the organization.
Request body:
{
"device": {
"site_id": "uuid",
"site_id": "uuid", # optional - if not provided, device is assigned directly to organization
"organization_id": "uuid", # optional - defaults to authenticated organization if not provided
"name": "Core Router",
"ip_address": "192.168.1.1",
"description": "Main router", # optional
"monitoring_enabled": true, # optional, default true
"snmp_enabled": true, # optional, default true
"snmp_version": "2c", # optional, default "2c"
"snmp_version": "2c", # optional, default "2c" (can be "1", "2c", or "3")
"snmp_community": "public", # optional, inherits from site/org if not set
"snmp_port": 161 # optional, default 161
"snmp_port": 161, # optional, default 161
# SNMPv3 fields (only used when snmp_version is "3"):
"snmpv3_security_level": "authPriv", # optional, one of: noAuthNoPriv, authNoPriv, authPriv
"snmpv3_username": "snmpuser", # optional
"snmpv3_auth_protocol": "SHA-256", # optional, one of: MD5, SHA, SHA-224, SHA-256, SHA-384, SHA-512
"snmpv3_auth_password": "authpass", # optional
"snmpv3_priv_protocol": "AES", # optional, one of: DES, AES, AES-192, AES-256
"snmpv3_priv_password": "privpass" # optional
}
}
@ -70,6 +78,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
"name": "Core Router",
"ip_address": "192.168.1.1",
"site_id": "uuid",
"organization_id": "uuid",
"monitoring_enabled": true,
"snmp_enabled": true,
"inserted_at": "2026-01-15T19:44:25Z"
@ -79,6 +88,14 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
organization_id = conn.assigns.current_organization_id
current_user = conn.assigns[:current_user]
# Add organization_id to params if not provided
device_params =
if Map.has_key?(device_params, "organization_id") do
device_params
else
Map.put(device_params, "organization_id", organization_id)
end
# Verify site belongs to organization if site_id is provided
case verify_site_access(device_params["site_id"], organization_id) do
:ok ->
@ -248,7 +265,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
# Private helpers
defp verify_site_access(nil, _organization_id), do: {:error, "site_id is required"}
defp verify_site_access(nil, _organization_id), do: :ok
defp verify_site_access(site_id, organization_id) do
site = Towerops.Sites.get_site!(site_id)
@ -269,6 +286,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
name: device.name,
ip_address: device.ip_address,
site_id: device.site_id,
organization_id: device.organization_id,
monitoring_enabled: device.monitoring_enabled,
snmp_enabled: device.snmp_enabled,
inserted_at: device.inserted_at
@ -281,6 +299,7 @@ defmodule ToweropsWeb.Api.V1.DevicesController do
name: device.name,
ip_address: device.ip_address,
site_id: device.site_id,
organization_id: device.organization_id,
description: device.description,
monitoring_enabled: device.monitoring_enabled,
check_interval_seconds: device.check_interval_seconds,

View file

@ -34,8 +34,9 @@ defmodule ToweropsWeb.DashboardLive do
end
defp load_dashboard_data(socket, organization_id) do
organization = socket.assigns.current_scope.organization
active_alerts = Alerts.list_organization_active_alerts(organization_id)
sites_count = length(Sites.list_organization_sites(organization_id))
sites_count = if organization.use_sites, do: length(Sites.list_organization_sites(organization_id)), else: 0
devices = Devices.list_organization_devices(organization_id)
devices_by_status =

View file

@ -8,7 +8,7 @@
<:subtitle>Welcome to {@current_scope.organization.name}</:subtitle>
</.header>
<%= if @sites_count == 0 do %>
<%= if @device_count == 0 do %>
<div class="mt-8 rounded-lg border-2 border-dashed border-gray-300 bg-gray-50 p-12 text-center dark:border-white/20 dark:bg-gray-800/50">
<.icon
name="hero-light-bulb"
@ -22,31 +22,45 @@
</p>
<div class="mt-8 max-w-2xl mx-auto">
<div class="space-y-4 text-left">
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 font-semibold dark:bg-blue-900 dark:text-blue-300">
1
<%= if @current_scope.organization.use_sites do %>
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 font-semibold dark:bg-blue-900 dark:text-blue-300">
1
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">Create a Site</h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Sites represent your physical locations (offices, data centers, towers, etc.)
</p>
</div>
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">Create a Site</h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Sites represent your physical locations (offices, data centers, towers, etc.)
</p>
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-600 font-semibold dark:bg-gray-700 dark:text-gray-400">
2
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">Add Devices</h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Add network devices (routers, switches, servers) to your sites
</p>
</div>
</div>
</div>
<% else %>
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-blue-100 text-blue-600 font-semibold dark:bg-blue-900 dark:text-blue-300">
1
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">Add Devices</h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Add network devices (routers, switches, servers) to monitor
</p>
</div>
</div>
<% end %>
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-600 font-semibold dark:bg-gray-700 dark:text-gray-400">
2
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">Add Device</h4>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
Add network devices (routers, switches, servers) to your sites
</p>
</div>
</div>
<div class="flex gap-4 items-start">
<div class="flex-shrink-0 flex items-center justify-center w-8 h-8 rounded-full bg-gray-200 text-gray-600 font-semibold dark:bg-gray-700 dark:text-gray-400">
3
{if @current_scope.organization.use_sites, do: "3", else: "2"}
</div>
<div>
<h4 class="font-medium text-gray-900 dark:text-white">
@ -60,21 +74,33 @@
</div>
</div>
<div class="mt-8">
<.button navigate={~p"/sites/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Create Your First Site
</.button>
<%= if @current_scope.organization.use_sites do %>
<.button navigate={~p"/sites/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Create Your First Site
</.button>
<% else %>
<.button navigate={~p"/devices/new"} variant="primary">
<.icon name="hero-plus" class="h-5 w-5" /> Add Your First Device
</.button>
<% end %>
</div>
</div>
<% else %>
<div class="mt-8 grid gap-6 md:grid-cols-2 lg:grid-cols-4">
<.link
navigate={~p"/sites"}
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-white/10 dark:bg-gray-800/50"
>
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Sites</h3>
<p class="mt-2 text-3xl font-bold text-gray-900 dark:text-white">{@sites_count}</p>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">Total sites</p>
</.link>
<div class={[
"mt-8 grid gap-6",
(@current_scope.organization.use_sites && "md:grid-cols-2 lg:grid-cols-4") ||
"md:grid-cols-3"
]}>
<%= if @current_scope.organization.use_sites do %>
<.link
navigate={~p"/sites"}
class="rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md dark:border-white/10 dark:bg-gray-800/50"
>
<h3 class="text-sm font-medium text-gray-500 dark:text-gray-400">Sites</h3>
<p class="mt-2 text-3xl font-bold text-gray-900 dark:text-white">{@sites_count}</p>
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">Total sites</p>
</.link>
<% end %>
<.link
navigate={~p"/devices"}

View file

@ -153,7 +153,7 @@ defmodule ToweropsWeb.DeviceLive.Form do
mikrotik_config = Devices.get_mikrotik_config(device)
# Check if device is MikroTik (based on SNMP discovery)
device_with_snmp = Towerops.Repo.preload(device, :snmp_device)
device_with_snmp = Towerops.Repo.preload(device, :snmp_device, force: true)
is_mikrotik = mikrotik_device?(device_with_snmp)
# Determine monitoring mode based on current snmp_enabled value

View file

@ -588,9 +588,9 @@
</div>
</div>
</div>
<!-- MikroTik API Configuration -->
<%= if @live_action == :edit and @is_mikrotik_device and @current_scope.user.is_superuser do %>
<%= if @live_action == :edit and @is_mikrotik_device do %>
<!-- MikroTik API Configuration -->
<div
class="grid max-w-7xl grid-cols-1 gap-x-8 gap-y-10 px-4 py-16 sm:px-6 md:grid-cols-3 lg:px-8"
phx-hook="MikrotikPortSync"
@ -601,8 +601,15 @@
MikroTik API Configuration
</h2>
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Configure MikroTik RouterOS API access. Works alongside SNMP for enhanced device management.
Configure MikroTik RouterOS API access. Works alongside SNMP for enhanced device management. SSH is used for device backups.
</p>
<div class="mt-3 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
<p class="text-xs text-blue-700 dark:text-blue-300">
<.icon name="hero-beaker" class="h-4 w-4 inline" />
<strong>Experimental Feature:</strong>
MikroTik API integration is under active development.
</p>
</div>
</div>
<div class="md:col-span-2">
@ -671,15 +678,6 @@
</p>
</div>
</div>
<% else %>
<div class="col-span-full">
<div class="rounded-md bg-yellow-50 dark:bg-yellow-900/20 p-4">
<p class="text-sm text-yellow-700 dark:text-yellow-300">
<strong>⚠️ Security Warning:</strong>
Plain API (port 8728) sends credentials unencrypted. Use SSL (port 8729) whenever possible.
</p>
</div>
</div>
<% end %>
<% end %>
</div>

View file

@ -239,6 +239,13 @@
<p class="mt-1 text-sm/6 text-gray-500 dark:text-gray-400">
Set default MikroTik RouterOS API credentials for all devices in this organization. Only applies to devices detected as MikroTik.
</p>
<div class="mt-3 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
<p class="text-xs text-blue-700 dark:text-blue-300">
<.icon name="hero-beaker" class="h-4 w-4 inline" />
<strong>Experimental Feature:</strong>
MikroTik API integration is under active development.
</p>
</div>
<p class="mt-3 text-xs text-gray-500 dark:text-gray-400 italic">
<.icon name="hero-information-circle" class="h-4 w-4 inline" />
Hierarchy: Device > Site > Organization

View file

@ -270,6 +270,13 @@
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
Override organization MikroTik API defaults for all devices at this site. Leave blank to inherit from organization. Only applies to MikroTik devices.
</p>
<div class="mb-4 rounded-md bg-blue-50 dark:bg-blue-900/20 p-3">
<p class="text-xs text-blue-700 dark:text-blue-300">
<.icon name="hero-beaker" class="h-4 w-4 inline" />
<strong>Experimental Feature:</strong>
MikroTik API integration is under active development.
</p>
</div>
<.input
field={@form[:mikrotik_enabled]}

View file

@ -0,0 +1,32 @@
defmodule Towerops.Repo.Migrations.RevertMikrotikUsernameEncryption do
use Ecto.Migration
def up do
# Revert mikrotik_username from encrypted binary to plaintext string
# Usernames are not sensitive enough to require encryption (passwords still encrypted)
# This fixes form display issues with encrypted binary fields
# Organizations
execute "ALTER TABLE organizations ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
# Sites
execute "ALTER TABLE sites ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
# Devices
execute "ALTER TABLE devices ALTER COLUMN mikrotik_username TYPE varchar USING NULL"
end
def down do
# Change back to binary for encryption
# Note: This will clear any existing usernames
# Organizations
execute "ALTER TABLE organizations ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
# Sites
execute "ALTER TABLE sites ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
# Devices
execute "ALTER TABLE devices ALTER COLUMN mikrotik_username TYPE bytea USING NULL"
end
end

View file

@ -5,6 +5,7 @@ Devices Tested & Working
2026-02-04
* Feature: SNMP v3 support
* Sites are now optional and not enabled by default
2026-02-03
* More tests

View file

@ -122,6 +122,7 @@ defmodule Towerops.Integration.SnmpIntegrationTest do
name: "SNMP Router",
ip_address: "192.0.2.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",

View file

@ -52,7 +52,8 @@ defmodule Towerops.AgentsFixturesTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, agent_token, _token_string} = agent_token_fixture(organization.id)

View file

@ -11,12 +11,10 @@ defmodule Towerops.DevicesFixtures do
def device_fixture(attrs \\ %{}) do
# Ensure organization and site exist
organization =
attrs[:organization] || Map.get(attrs, "organization") ||
Towerops.OrganizationsFixtures.organization_fixture()
attrs[:organization] || Map.get(attrs, "organization") || create_organization()
site =
attrs[:site] || Map.get(attrs, "site") ||
Towerops.OrganizationsFixtures.site_fixture(%{organization_id: organization.id})
attrs[:site] || Map.get(attrs, "site") || create_site(organization)
# Default device attributes
default_attrs = %{
@ -43,4 +41,15 @@ defmodule Towerops.DevicesFixtures do
defp to_atom_key(key) when is_atom(key), do: key
defp to_atom_key(key) when is_binary(key), do: String.to_existing_atom(key)
defp create_organization do
user = Towerops.AccountsFixtures.user_fixture()
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
organization
end
defp create_site(organization) do
{:ok, site} = Towerops.Sites.create_site(%{name: "Test Site", organization_id: organization.id})
site
end
end

View file

@ -81,6 +81,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -122,6 +123,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -135,6 +137,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -219,6 +222,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -249,6 +253,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -279,6 +284,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c",
@ -302,6 +308,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Unmonitored Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c",
@ -328,6 +335,7 @@ defmodule Towerops.Agents.StatsTest do
name: "SNMP Disabled",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: false,
monitoring_enabled: true
})
@ -347,6 +355,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Monitoring Disabled",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c",
@ -385,6 +394,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -466,6 +476,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -488,6 +499,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -551,6 +563,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -598,6 +611,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -642,6 +656,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -701,6 +716,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -752,6 +768,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -777,6 +794,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -818,6 +836,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -867,6 +886,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -918,6 +938,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -968,6 +989,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -1011,6 +1033,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Non-SNMP Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: false
})
@ -1042,6 +1065,7 @@ defmodule Towerops.Agents.StatsTest do
name: "Unassigned Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"

View file

@ -126,7 +126,7 @@ defmodule Towerops.AgentsTest do
%{site: site, agent_token: agent_token}
end
test "deletes agent token from database", %{agent_token: agent_token} do
test "deletes agent token from database", %{agent_token: agent_token, organization: _organization} do
assert {:ok, _} = Agents.delete_agent_token(agent_token.id)
assert_raise Ecto.NoResultsError, fn ->
@ -136,20 +136,23 @@ defmodule Towerops.AgentsTest do
test "removes all direct device assignments", %{
site: site,
agent_token: agent_token
agent_token: agent_token,
organization: organization
} do
{:ok, device1} =
Towerops.Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Assign both devices to the agent
@ -181,7 +184,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Directly assign device to agent_to_delete (overriding site default)
@ -215,7 +219,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Directly assign device to agent_to_delete (overriding org default)
@ -234,6 +239,7 @@ defmodule Towerops.AgentsTest do
end
test "devices have no agent when no fallback exists (cloud polling)", %{
organization: organization,
site: site,
agent_token: agent_token
} do
@ -241,7 +247,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Directly assign device to agent (no site or org defaults)
@ -277,7 +284,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Device should use site agent
@ -309,7 +317,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Device should use org default
@ -356,7 +365,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
%{site: site, device: device}
@ -399,7 +409,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, agent_token, _token} = Agents.create_agent_token(org.id, "Test Agent")
@ -407,7 +418,7 @@ defmodule Towerops.AgentsTest do
%{site: site, device: device, agent_token: agent_token}
end
test "removes equipment assignment", %{agent_token: agent_token, device: device} do
test "removes equipment assignment", %{agent_token: agent_token, device: device, organization: _organization} do
{:ok, _assignment} = Agents.assign_device_to_agent(agent_token.id, device.id)
{1, _} = Agents.unassign_device(device.id)
@ -465,7 +476,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, agent_token, _token} = Agents.create_agent_token(org.id, "Test Agent")
@ -483,7 +495,7 @@ defmodule Towerops.AgentsTest do
assert retrieved_assignment.id == assignment.id
end
test "returns nil when equipment is not assigned", %{device: device} do
test "returns nil when equipment is not assigned", %{device: device, organization: _organization} do
assert Agents.get_device_assignment(device.id) == nil
end
end
@ -500,7 +512,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, agent_token1, _} = Agents.create_agent_token(org.id, "Agent 1")
@ -562,21 +575,24 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Test Equipment 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, device3} =
Towerops.Devices.create_device(%{
name: "Test Equipment 3",
ip_address: "192.168.1.3",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
%{site: site, device1: device1, device2: device2, device3: device3}
@ -621,7 +637,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
%{
@ -686,7 +703,7 @@ defmodule Towerops.AgentsTest do
assert Agents.get_effective_agent_token(device) == agent1.id
end
test "returns nil when no agent assigned at any level", %{device: device} do
test "returns nil when no agent assigned at any level", %{device: device, organization: _organization} do
# No assignments at any level
device = Repo.preload(device, site: [organization: :default_agent_token])
@ -714,7 +731,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment 2",
ip_address: "192.168.2.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
device = Repo.preload(device, site: [organization: :default_agent_token])
@ -764,7 +782,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
%{
@ -814,7 +833,7 @@ defmodule Towerops.AgentsTest do
assert agent_id == agent1.id
end
test "returns none source when no assignment", %{device: device} do
test "returns none source when no assignment", %{device: device, organization: _organization} do
device = Repo.preload(device, site: [organization: :default_agent_token])
assert {nil, :none} = Agents.get_effective_agent_token_with_source(device)
@ -839,7 +858,8 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment 3",
ip_address: "192.168.3.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
device = Repo.preload(device, site: [organization: :default_agent_token])
@ -864,21 +884,24 @@ defmodule Towerops.AgentsTest do
Towerops.Devices.create_device(%{
name: "Test Equipment 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Test Equipment 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
{:ok, device3} =
Towerops.Devices.create_device(%{
name: "Test Equipment 3",
ip_address: "192.168.1.3",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
%{site: site, device1: device1, device2: device2, device3: device3}
@ -948,6 +971,7 @@ defmodule Towerops.AgentsTest do
end
test "returns device with direct agent assignment", %{
organization: organization,
agent1: agent1,
agent2: agent2,
site1: site1
@ -957,6 +981,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -967,6 +992,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 2",
ip_address: "192.168.1.2",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -988,6 +1014,7 @@ defmodule Towerops.AgentsTest do
end
test "returns device inheriting from site agent", %{
organization: organization,
agent1: agent1,
agent2: agent2,
site1: site1
@ -1000,6 +1027,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1010,6 +1038,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 2",
ip_address: "192.168.1.2",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1041,6 +1070,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1051,6 +1081,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 2",
ip_address: "192.168.1.2",
site_id: site1.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1085,6 +1116,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1120,6 +1152,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1135,6 +1168,7 @@ defmodule Towerops.AgentsTest do
end
test "only returns SNMP-enabled devices", %{
organization: organization,
agent1: agent1,
site1: site1
} do
@ -1144,6 +1178,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1155,6 +1190,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 2",
ip_address: "192.168.1.2",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: false
})
@ -1189,6 +1225,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1200,6 +1237,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 2",
ip_address: "192.168.1.2",
site_id: site2.id,
organization_id: org.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1217,6 +1255,7 @@ defmodule Towerops.AgentsTest do
end
test "preloads necessary associations for API response", %{
organization: organization,
agent1: agent1,
site1: site1
} do
@ -1225,6 +1264,7 @@ defmodule Towerops.AgentsTest do
name: "Equipment 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1345,6 +1385,8 @@ defmodule Towerops.AgentsTest do
end
test "cloud poller can be assigned to devices across organizations", %{
org1: org1,
org2: org2,
site1: site1,
site2: site2,
cloud_poller: cloud_poller
@ -1355,6 +1397,7 @@ defmodule Towerops.AgentsTest do
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org1.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1365,6 +1408,7 @@ defmodule Towerops.AgentsTest do
name: "Device 2",
ip_address: "192.168.2.1",
site_id: site2.id,
organization_id: org2.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1405,6 +1449,7 @@ defmodule Towerops.AgentsTest do
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org1.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1415,6 +1460,7 @@ defmodule Towerops.AgentsTest do
name: "Device 2",
ip_address: "192.168.2.1",
site_id: site2.id,
organization_id: org2.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1428,6 +1474,8 @@ defmodule Towerops.AgentsTest do
end
test "cloud poller can be site default for sites in different orgs", %{
org1: org1,
org2: org2,
site1: site1,
site2: site2,
cloud_poller: cloud_poller
@ -1442,6 +1490,7 @@ defmodule Towerops.AgentsTest do
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: org1.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"
@ -1452,6 +1501,7 @@ defmodule Towerops.AgentsTest do
name: "Device 2",
ip_address: "192.168.2.1",
site_id: site2.id,
organization_id: org2.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public"

View file

@ -22,7 +22,8 @@ defmodule Towerops.AlertsTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
%{device: device, organization: organization, user: user}
@ -395,14 +396,16 @@ defmodule Towerops.AlertsTest do
Towerops.Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site2.id
site_id: site2.id,
organization_id: org2.id
})
%{org1: org1, org2: org2, device1: device1, device2: device2}

View file

@ -216,6 +216,7 @@ defmodule Towerops.Devices.BackupRequestsTest do
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
},

View file

@ -232,6 +232,7 @@ defmodule Towerops.Devices.DeviceFirmwareHistoryTest do
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"

View file

@ -325,6 +325,7 @@ defmodule Towerops.Devices.MikrotikBackupsTest do
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
},

View file

@ -38,8 +38,10 @@ defmodule Towerops.EquipmentTest do
}
@invalid_attrs %{name: nil, ip_address: nil}
test "list_site_devices/1 returns all devices for a site", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "list_site_devices/1 returns all devices for a site", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert Devices.list_site_devices(site.id) == [device]
end
@ -47,22 +49,27 @@ defmodule Towerops.EquipmentTest do
organization: organization,
site: site
} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
result = Devices.list_organization_devices(organization.id)
assert length(result) == 1
assert hd(result).id == device.id
end
test "list_monitored_devices/0 returns only device with monitoring enabled", %{
organization: organization,
site: site
} do
{:ok, monitored} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
{:ok, monitored} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
{:ok, _not_monitored} =
Devices.create_device(%{
name: "Not Monitored",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false
})
@ -71,19 +78,22 @@ defmodule Towerops.EquipmentTest do
assert hd(result).id == monitored.id
end
test "get_device!/1 returns the device with given id", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "get_device!/1 returns the device with given id", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert Devices.get_device!(device.id).id == device.id
end
test "get_site_equipment!/2 returns device for specific site", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "get_site_equipment!/2 returns device for specific site", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert Devices.get_site_device!(site.id, device.id).id == device.id
end
test "create_device/1 with valid data creates device", %{site: site} do
attrs = Map.put(@valid_attrs, :site_id, site.id)
test "create_device/1 with valid data creates device", %{organization: organization, site: site} do
attrs = Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id})
assert {:ok, %DeviceSchema{} = device} = Devices.create_device(attrs)
assert device.name == "Router 1"
assert device.ip_address == "192.168.1.1"
@ -92,15 +102,15 @@ defmodule Towerops.EquipmentTest do
assert device.check_interval_seconds == 300
end
test "create_device/1 with valid IPv6 address", %{site: site} do
test "create_device/1 with valid IPv6 address", %{organization: organization, site: site} do
attrs = Map.put(@valid_attrs, :ip_address, "2001:0db8:85a3::8a2e:0370:7334")
attrs = Map.put(attrs, :site_id, site.id)
attrs = Map.merge(attrs, %{site_id: site.id, organization_id: organization.id})
assert {:ok, %DeviceSchema{}} = Devices.create_device(attrs)
end
test "create_device/1 with invalid IP address returns error", %{site: site} do
test "create_device/1 with invalid IP address returns error", %{organization: organization, site: site} do
attrs = Map.put(@valid_attrs, :ip_address, "invalid-ip")
attrs = Map.put(attrs, :site_id, site.id)
attrs = Map.merge(attrs, %{site_id: site.id, organization_id: organization.id})
assert {:error, changeset} = Devices.create_device(attrs)
assert "must be a valid IPv4 or IPv6 address" in errors_on(changeset).ip_address
end
@ -109,8 +119,9 @@ defmodule Towerops.EquipmentTest do
assert {:error, %Ecto.Changeset{}} = Devices.create_device(@invalid_attrs)
end
test "update_device/2 with valid data updates the device", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "update_device/2 with valid data updates the device", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert {:ok, %DeviceSchema{} = device} =
Devices.update_device(device, @update_attrs)
@ -120,8 +131,9 @@ defmodule Towerops.EquipmentTest do
assert device.monitoring_enabled == false
end
test "update_device/2 with invalid data returns error changeset", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "update_device/2 with invalid data returns error changeset", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert {:error, %Ecto.Changeset{}} =
Devices.update_device(device, @invalid_attrs)
@ -129,13 +141,16 @@ defmodule Towerops.EquipmentTest do
assert Devices.get_device!(device.id).name == device.name
end
test "change_device/1 returns an device changeset", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "change_device/1 returns an device changeset", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert %Ecto.Changeset{} = Devices.change_device(device)
end
test "update_device_status/2 updates status and timestamps", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "update_device_status/2 updates status and timestamps", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert {:ok, updated} = Devices.update_device_status(device, :up)
assert updated.status == :up
@ -144,9 +159,12 @@ defmodule Towerops.EquipmentTest do
end
test "update_device_status/2 only updates last_checked_at if status unchanged", %{
organization: organization,
site: site
} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
{:ok, device} = Devices.update_device_status(device, :up)
# Manually set timestamps to 1 hour ago to avoid sleeping
@ -171,12 +189,13 @@ defmodule Towerops.EquipmentTest do
assert DateTime.after?(updated.last_checked_at, first_checked_at)
end
test "list_snmp_enabled_devices/0 returns only device with SNMP enabled", %{site: site} do
test "list_snmp_enabled_devices/0 returns only device with SNMP enabled", %{organization: organization, site: site} do
{:ok, snmp_enabled} =
Devices.create_device(%{
name: "SNMP Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: "public"
})
@ -186,6 +205,7 @@ defmodule Towerops.EquipmentTest do
name: "Non-SNMP Router",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: false
})
@ -194,8 +214,10 @@ defmodule Towerops.EquipmentTest do
assert hd(result).id == snmp_enabled.id
end
test "update_snmp_poll_time/1 updates last_snmp_poll_at timestamp", %{site: site} do
{:ok, device} = Devices.create_device(Map.put(@valid_attrs, :site_id, site.id))
test "update_snmp_poll_time/1 updates last_snmp_poll_at timestamp", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(Map.merge(@valid_attrs, %{site_id: site.id, organization_id: organization.id}))
assert device.last_snmp_poll_at == nil
assert {:ok, updated} = Devices.update_snmp_poll_time(device)
@ -203,11 +225,12 @@ defmodule Towerops.EquipmentTest do
assert DateTime.before?(updated.last_snmp_poll_at, DateTime.utc_now())
end
test "create_device/1 with SNMP enabled and nil community string succeeds", %{site: site} do
test "create_device/1 with SNMP enabled and nil community string succeeds", %{organization: organization, site: site} do
attrs = %{
name: "SNMP Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: nil
@ -218,11 +241,15 @@ defmodule Towerops.EquipmentTest do
assert device.snmp_community == nil
end
test "create_device/1 with SNMP enabled and empty community string succeeds", %{site: site} do
test "create_device/1 with SNMP enabled and empty community string succeeds", %{
organization: organization,
site: site
} do
attrs = %{
name: "SNMP Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: ""
@ -235,10 +262,30 @@ defmodule Towerops.EquipmentTest do
assert device.snmp_community == nil || device.snmp_community == ""
end
test "reorder_device/2 reorders device to first position", %{site: site} do
{:ok, device1} = Devices.create_device(%{name: "Device 1", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Device 2", ip_address: "192.168.1.2", site_id: site.id})
{:ok, device3} = Devices.create_device(%{name: "Device 3", ip_address: "192.168.1.3", site_id: site.id})
test "reorder_device/2 reorders device to first position", %{organization: organization, site: site} do
{:ok, device1} =
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
{:ok, device3} =
Devices.create_device(%{
name: "Device 3",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id
})
# Move device3 to first position
{:ok, _updated} = Devices.reorder_device(device3.id, 1)
@ -255,10 +302,30 @@ defmodule Towerops.EquipmentTest do
assert Enum.at(devices, 2).display_order == 3
end
test "reorder_device/2 reorders device to last position", %{site: site} do
{:ok, device1} = Devices.create_device(%{name: "Device 1", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Device 2", ip_address: "192.168.1.2", site_id: site.id})
{:ok, device3} = Devices.create_device(%{name: "Device 3", ip_address: "192.168.1.3", site_id: site.id})
test "reorder_device/2 reorders device to last position", %{organization: organization, site: site} do
{:ok, device1} =
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
{:ok, device3} =
Devices.create_device(%{
name: "Device 3",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id
})
# Move device1 to last position
{:ok, _updated} = Devices.reorder_device(device1.id, 3)
@ -275,10 +342,30 @@ defmodule Towerops.EquipmentTest do
assert Enum.at(devices, 2).display_order == 3
end
test "reorder_device/2 reorders device to middle position", %{site: site} do
{:ok, device1} = Devices.create_device(%{name: "Device 1", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Device 2", ip_address: "192.168.1.2", site_id: site.id})
{:ok, device3} = Devices.create_device(%{name: "Device 3", ip_address: "192.168.1.3", site_id: site.id})
test "reorder_device/2 reorders device to middle position", %{organization: organization, site: site} do
{:ok, device1} =
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
{:ok, device3} =
Devices.create_device(%{
name: "Device 3",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id
})
# Move device3 to middle position
{:ok, _updated} = Devices.reorder_device(device3.id, 2)
@ -295,11 +382,38 @@ defmodule Towerops.EquipmentTest do
assert Enum.at(devices, 2).display_order == 3
end
test "reorder_device/2 maintains continuous numbering", %{site: site} do
{:ok, _device1} = Devices.create_device(%{name: "Device 1", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Device 2", ip_address: "192.168.1.2", site_id: site.id})
{:ok, _device3} = Devices.create_device(%{name: "Device 3", ip_address: "192.168.1.3", site_id: site.id})
{:ok, device4} = Devices.create_device(%{name: "Device 4", ip_address: "192.168.1.4", site_id: site.id})
test "reorder_device/2 maintains continuous numbering", %{organization: organization, site: site} do
{:ok, _device1} =
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
{:ok, _device3} =
Devices.create_device(%{
name: "Device 3",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id
})
{:ok, device4} =
Devices.create_device(%{
name: "Device 4",
ip_address: "192.168.1.4",
site_id: site.id,
organization_id: organization.id
})
# Reorder multiple times
{:ok, _} = Devices.reorder_device(device2.id, 1)
@ -313,12 +427,32 @@ defmodule Towerops.EquipmentTest do
test "reorder_device/2 only affects devices in same site", %{organization: organization, site: site} do
# Create devices in first site
{:ok, device1} = Devices.create_device(%{name: "Device 1", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Device 2", ip_address: "192.168.1.2", site_id: site.id})
{:ok, device1} =
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
# Create second site and device
{:ok, site2} = Towerops.Sites.create_site(%{name: "Site 2", organization_id: organization.id})
{:ok, device3} = Devices.create_device(%{name: "Device 3", ip_address: "192.168.2.1", site_id: site2.id})
{:ok, device3} =
Devices.create_device(%{
name: "Device 3",
ip_address: "192.168.2.1",
site_id: site2.id,
organization_id: organization.id
})
# Reorder device in first site
{:ok, _} = Devices.reorder_device(device1.id, 2)
@ -339,8 +473,21 @@ defmodule Towerops.EquipmentTest do
organization: organization,
site: site
} do
{:ok, device1} = Devices.create_device(%{name: "Zebra Device", ip_address: "192.168.1.1", site_id: site.id})
{:ok, device2} = Devices.create_device(%{name: "Alpha Device", ip_address: "192.168.1.2", site_id: site.id})
{:ok, device1} =
Devices.create_device(%{
name: "Zebra Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Alpha Device",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id
})
# Set custom order
{:ok, _} = Devices.reorder_device(device1.id, 1)
@ -380,12 +527,13 @@ defmodule Towerops.EquipmentTest do
%{organization: organization, site: site, user: user}
end
test "get_snmp_config/1 returns device-level community string when set", %{site: site} do
test "get_snmp_config/1 returns device-level community string when set", %{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_enabled: true,
snmp_community: "device-community"
})
@ -396,7 +544,7 @@ defmodule Towerops.EquipmentTest do
end
test "get_snmp_config/1 returns site-level community when device has nil", %{
organization: _organization,
organization: organization,
site: site
} do
# Update site with SNMP community
@ -411,6 +559,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: nil
})
@ -440,6 +589,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: nil
})
@ -450,12 +600,13 @@ defmodule Towerops.EquipmentTest do
assert config.source == :organization
end
test "get_snmp_config/1 returns nil when all levels have nil", %{site: site} do
test "get_snmp_config/1 returns nil when all levels have 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,
snmp_enabled: true,
snmp_community: nil
})
@ -463,7 +614,7 @@ defmodule Towerops.EquipmentTest do
device = Towerops.Repo.preload(device, site: :organization)
config = Devices.get_snmp_config(device)
assert config.community == nil
assert config.source == :default
assert config.source == :organization
end
test "get_snmp_config/1 prioritizes device over site over org", %{
@ -488,6 +639,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: "device-community"
})
@ -519,7 +671,8 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
%{device: device}
@ -612,12 +765,13 @@ defmodule Towerops.EquipmentTest do
%{organization: organization, site: site, user: user}
end
test "get_device/1 returns device when exists", %{site: site} do
test "get_device/1 returns device when exists", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
found = Devices.get_device(device.id)
@ -629,12 +783,13 @@ defmodule Towerops.EquipmentTest do
assert Devices.get_device(Ecto.UUID.generate()) == nil
end
test "get_device_with_details/1 preloads associations", %{site: site} do
test "get_device_with_details/1 preloads associations", %{organization: organization, site: site} do
{:ok, device} =
Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
result = Devices.get_device_with_details(device.id)
@ -643,13 +798,14 @@ defmodule Towerops.EquipmentTest do
end
@tag :skip
test "delete_device/1 deletes the device", %{site: site} do
test "delete_device/1 deletes the device", %{organization: organization, site: site} do
# Skipped: Requires Monitoring.Supervisor to be running
{:ok, device} =
Devices.create_device(%{
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
assert {:ok, _deleted} = Devices.delete_device(device)
@ -661,50 +817,56 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
assert Devices.count_organization_devices(organization.id) == 2
end
test "count_site_devices/1 returns correct count", %{site: site} do
test "count_site_devices/1 returns correct count", %{organization: organization, site: site} do
{:ok, _device1} =
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
assert Devices.count_site_devices(site.id) == 2
end
test "count_site_devices_down/1 returns correct count", %{site: site} do
test "count_site_devices_down/1 returns correct count", %{organization: organization, site: site} do
{:ok, device1} =
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Mark one as down
@ -724,14 +886,16 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site2.id
site_id: site2.id,
organization_id: organization.id
})
result = Devices.list_organization_devices(organization.id, %{"site_id" => site.id})
@ -744,14 +908,16 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Mark one as up
@ -762,12 +928,13 @@ defmodule Towerops.EquipmentTest do
assert hd(result).id == device1.id
end
test "get_snmp_config/1 with device ID string", %{site: site} do
test "get_snmp_config/1 with 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_enabled: true,
snmp_community: "test-community"
})
@ -794,12 +961,13 @@ defmodule Towerops.EquipmentTest do
%{organization: organization, site: site, user: user}
end
test "triggers discovery when snmp_enabled changes from false to true", %{site: site} do
test "triggers discovery when snmp_enabled changes from false to true", %{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_enabled: false
})
@ -819,12 +987,13 @@ defmodule Towerops.EquipmentTest do
assert job.args["device_id"] == device.id
end
test "triggers discovery when snmp_version changes while enabled", %{site: site} do
test "triggers discovery when snmp_version changes while enabled", %{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_enabled: true,
snmp_version: "2c"
})
@ -845,12 +1014,13 @@ defmodule Towerops.EquipmentTest do
assert job.args["device_id"] == device.id
end
test "triggers discovery when snmp_port changes while enabled", %{site: site} do
test "triggers discovery when snmp_port changes while enabled", %{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_enabled: true,
snmp_port: 161
})
@ -872,6 +1042,7 @@ defmodule Towerops.EquipmentTest do
end
test "triggers discovery when monitoring_enabled changes to true with SNMP enabled", %{
organization: organization,
site: site
} do
{:ok, device} =
@ -879,6 +1050,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
monitoring_enabled: false
})
@ -900,6 +1072,7 @@ defmodule Towerops.EquipmentTest do
end
test "does not trigger discovery when monitoring_enabled changes but SNMP disabled", %{
organization: organization,
site: site
} do
{:ok, device} =
@ -907,6 +1080,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: false,
monitoring_enabled: false
})
@ -926,12 +1100,13 @@ defmodule Towerops.EquipmentTest do
assert [] = jobs
end
test "does not trigger discovery when SNMP is disabled", %{site: site} do
test "does not trigger discovery when SNMP is disabled", %{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_enabled: true,
snmp_version: "2c"
})
@ -951,12 +1126,13 @@ defmodule Towerops.EquipmentTest do
assert [] = jobs
end
test "does not trigger discovery for unrelated field changes", %{site: site} do
test "does not trigger discovery for unrelated field changes", %{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_enabled: true
})
@ -975,12 +1151,13 @@ defmodule Towerops.EquipmentTest do
assert [] = jobs
end
test "does not trigger discovery when snmp_version changes while disabled", %{site: site} do
test "does not trigger discovery when snmp_version changes while disabled", %{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_enabled: false,
snmp_version: "2c"
})
@ -1028,7 +1205,8 @@ defmodule Towerops.EquipmentTest do
attrs = %{
name: name,
ip_address: ip_address,
site_id: site.id
site_id: site.id,
organization_id: organization.id
}
# Use bypass_limits since property tests may create > 10 devices
@ -1053,7 +1231,8 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.100",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Update status twice
@ -1083,7 +1262,8 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Device #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
end
end
@ -1129,6 +1309,7 @@ defmodule Towerops.EquipmentTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_community: device_community
})
@ -1168,7 +1349,7 @@ defmodule Towerops.EquipmentTest do
%{organization: organization, site: site, user: user}
end
test "blocks device creation when at 10-device limit", %{site: site} do
test "blocks device creation when at 10-device limit", %{organization: organization, site: site} do
# Create 10 devices (the limit)
for i <- 1..10 do
{:ok, _device} =
@ -1176,6 +1357,7 @@ defmodule Towerops.EquipmentTest do
name: "Device #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1187,6 +1369,7 @@ defmodule Towerops.EquipmentTest do
name: "Device 11",
ip_address: "192.168.1.11",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1195,7 +1378,7 @@ defmodule Towerops.EquipmentTest do
errors_on(changeset)
end
test "allows device creation when under limit", %{site: site} do
test "allows device creation when under limit", %{organization: organization, site: site} do
# Create 9 devices
for i <- 1..9 do
{:ok, _device} =
@ -1203,6 +1386,7 @@ defmodule Towerops.EquipmentTest do
name: "Device #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1214,12 +1398,13 @@ defmodule Towerops.EquipmentTest do
name: "Device 10",
ip_address: "192.168.1.10",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
end
test "bypasses limit with bypass_limits option", %{site: site} do
test "bypasses limit with bypass_limits option", %{organization: organization, site: site} do
# Create 10 devices (at limit)
for i <- 1..10 do
{:ok, _device} =
@ -1227,6 +1412,7 @@ defmodule Towerops.EquipmentTest do
name: "Device #{i}",
ip_address: "192.168.1.#{i}",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1239,6 +1425,7 @@ defmodule Towerops.EquipmentTest do
name: "Device 11",
ip_address: "192.168.1.11",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
},
@ -1263,6 +1450,7 @@ defmodule Towerops.EquipmentTest do
name: "Site1 Device #{i}",
ip_address: "192.168.1.#{i}",
site_id: site1.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1275,6 +1463,7 @@ defmodule Towerops.EquipmentTest do
name: "Site2 Device #{i}",
ip_address: "192.168.2.#{i}",
site_id: site2.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1286,6 +1475,7 @@ defmodule Towerops.EquipmentTest do
name: "Site1 Device 7",
ip_address: "192.168.1.7",
site_id: site1.id,
organization_id: organization.id,
monitoring_enabled: false,
snmp_enabled: false
})
@ -1331,14 +1521,16 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
{:ok, device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site2.id
site_id: site2.id,
organization_id: org2.id
})
result = Devices.list_devices_for_organizations([org1.id, org2.id])
@ -1351,6 +1543,7 @@ defmodule Towerops.EquipmentTest do
test "returns only devices from specified organizations", %{
org1: org1,
org2: org2,
site1: site1,
site2: site2
} do
@ -1358,14 +1551,16 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
{:ok, _device2} =
Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site2.id
site_id: site2.id,
organization_id: org2.id
})
# Only request devices from org1
@ -1389,7 +1584,8 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
result = Devices.list_devices_for_organizations([org1.id])
@ -1404,21 +1600,24 @@ defmodule Towerops.EquipmentTest do
Devices.create_device(%{
name: "Zebra Device",
ip_address: "192.168.1.1",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
{:ok, _alpha_dev} =
Devices.create_device(%{
name: "Alpha Device",
ip_address: "192.168.1.2",
site_id: site1.id
site_id: site1.id,
organization_id: org1.id
})
{:ok, _beta_dev} =
Devices.create_device(%{
name: "Beta Device",
ip_address: "192.168.2.1",
site_id: site2.id
site_id: site2.id,
organization_id: org2.id
})
result = Devices.list_devices_for_organizations([org1.id, org2.id])

View file

@ -25,6 +25,7 @@ defmodule Towerops.Devices.EventLoggerTest do
Towerops.Devices.create_device(%{
name: "Test Equipment",
site_id: site.id,
organization_id: site.organization_id,
ip_address: "192.168.1.1"
})

View file

@ -21,7 +21,8 @@ defmodule Towerops.Monitoring.CheckTest do
Towerops.Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: site.organization_id
})
{:ok, device: device}

View file

@ -22,7 +22,8 @@ defmodule Towerops.MonitoringTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
%{device: device}

View file

@ -196,12 +196,15 @@ defmodule Towerops.Organizations.SubscriptionLimitsTest do
# Helper functions
defp create_device(site_id, name, ip_address) do
site = Sites.get_site!(site_id)
{:ok, device} =
Devices.create_device(
%{
name: name,
ip_address: ip_address,
site_id: site_id,
organization_id: site.organization_id,
monitoring_enabled: false,
snmp_enabled: false
},

View file

@ -437,7 +437,8 @@ defmodule Towerops.OrganizationsTest do
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
@ -447,7 +448,8 @@ defmodule Towerops.OrganizationsTest do
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{count, _} = Organizations.apply_snmp_config_to_all_equipment(organization.id)
@ -494,14 +496,16 @@ defmodule Towerops.OrganizationsTest do
Towerops.Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{count, _} = Organizations.apply_agent_to_all_equipment(organization.id)
@ -528,7 +532,8 @@ defmodule Towerops.OrganizationsTest do
Towerops.Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, agent_token, _token} =
@ -562,7 +567,8 @@ defmodule Towerops.OrganizationsTest do
Towerops.Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
Towerops.Agents.assign_device_to_agent(old_agent.id, device.id)
@ -678,6 +684,7 @@ defmodule Towerops.OrganizationsTest do
name: "MikroTik Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
manufacturer: "MikroTik"
})
@ -696,9 +703,9 @@ defmodule Towerops.OrganizationsTest do
assert updated_org.mikrotik_use_ssl == true
assert updated_org.mikrotik_enabled == true
# Verify device credential source remains "site" (inherits from site, which inherits from org)
# Verify device credential source is "organization" (inherits directly from org)
updated_device = Repo.get!(Device, device.id)
assert updated_device.mikrotik_credential_source == "site"
assert updated_device.mikrotik_credential_source == "organization"
# Verify the device received the organization's MikroTik settings through propagation
assert updated_device.mikrotik_username == "admin"
@ -731,6 +738,7 @@ defmodule Towerops.OrganizationsTest do
name: "MikroTik Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
manufacturer: "MikroTik"
})

View file

@ -54,7 +54,13 @@ defmodule Towerops.SitesTest do
test "list_root_sites/1 returns only sites without parents", %{organization: organization} do
{:ok, root} = Sites.create_site(%{name: "Root Site", organization_id: organization.id})
{:ok, _child} = Sites.create_site(%{name: "Child Site", organization_id: organization.id, parent_site_id: root.id})
{:ok, _child} =
Sites.create_site(%{
name: "Child Site",
organization_id: organization.id,
parent_site_id: root.id
})
roots = Sites.list_root_sites(organization.id)
assert length(roots) == 1
@ -63,8 +69,20 @@ defmodule Towerops.SitesTest do
test "list_child_sites/1 returns child sites of a parent", %{organization: organization} do
{:ok, parent} = Sites.create_site(%{name: "Parent", organization_id: organization.id})
{:ok, child1} = Sites.create_site(%{name: "Child 1", organization_id: organization.id, parent_site_id: parent.id})
{:ok, child2} = Sites.create_site(%{name: "Child 2", organization_id: organization.id, parent_site_id: parent.id})
{:ok, child1} =
Sites.create_site(%{
name: "Child 1",
organization_id: organization.id,
parent_site_id: parent.id
})
{:ok, child2} =
Sites.create_site(%{
name: "Child 2",
organization_id: organization.id,
parent_site_id: parent.id
})
children = Sites.list_child_sites(parent.id)
assert length(children) == 2
@ -81,7 +99,13 @@ defmodule Towerops.SitesTest do
test "get_site!/1 preloads associations", %{organization: organization} do
{:ok, parent} = Sites.create_site(%{name: "Parent", organization_id: organization.id})
{:ok, site} = Sites.create_site(%{name: "Child", organization_id: organization.id, parent_site_id: parent.id})
{:ok, site} =
Sites.create_site(%{
name: "Child",
organization_id: organization.id,
parent_site_id: parent.id
})
fetched = Sites.get_site!(site.id)
assert Ecto.assoc_loaded?(fetched.parent_site)
@ -195,7 +219,7 @@ defmodule Towerops.SitesTest do
{:ok, site} = Sites.create_site(%{name: "Test Site", organization_id: organization.id})
# Try to make site its own parent
assert {:error, changeset} = Sites.update_site(site, %{parent_site_id: site.id})
assert {:error, changeset} = Sites.update_site(site, %{parent_site_id: site.id, organization_id: organization.id})
assert "cannot be the same as the site itself" in errors_on(changeset).parent_site_id
end
@ -232,10 +256,20 @@ defmodule Towerops.SitesTest do
test "build_site_tree/1 builds hierarchical structure", %{organization: organization} do
{:ok, root1} = Sites.create_site(%{name: "Root 1", organization_id: organization.id})
{:ok, root2} = Sites.create_site(%{name: "Root 2", organization_id: organization.id})
{:ok, child1} = Sites.create_site(%{name: "Child 1", organization_id: organization.id, parent_site_id: root1.id})
{:ok, child1} =
Sites.create_site(%{
name: "Child 1",
organization_id: organization.id,
parent_site_id: root1.id
})
{:ok, grandchild} =
Sites.create_site(%{name: "Grandchild", organization_id: organization.id, parent_site_id: child1.id})
Sites.create_site(%{
name: "Grandchild",
organization_id: organization.id,
parent_site_id: child1.id
})
tree = Sites.build_site_tree(organization.id)
assert length(tree) == 2
@ -269,6 +303,7 @@ defmodule Towerops.SitesTest do
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_version: "1",
snmp_community: "old-community"
})
@ -278,6 +313,7 @@ defmodule Towerops.SitesTest do
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id,
snmp_version: "1",
snmp_community: "old-community"
})
@ -308,14 +344,16 @@ defmodule Towerops.SitesTest do
Towerops.Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Device 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{count, _} = Sites.apply_agent_to_all_equipment(site.id)
@ -345,7 +383,8 @@ defmodule Towerops.SitesTest do
Towerops.Devices.create_device(%{
name: "Device 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Create initial assignment

View file

@ -37,24 +37,26 @@ defmodule Towerops.Snmp.DiscoveryTest do
end
describe "discover_device/1" do
test "returns error when SNMP is not enabled", %{site: site} do
test "returns error when SNMP is not enabled", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: false
})
assert {:error, :snmp_not_enabled} = Discovery.discover_device(device)
end
test "successfully discovers MikroTik device", %{site: site} do
test "successfully discovers MikroTik device", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "MikroTik Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -205,12 +207,13 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert interface1.if_alias == "WAN"
end
test "successfully discovers Cisco device", %{site: site} do
test "successfully discovers Cisco device", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Cisco Switch",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -257,12 +260,13 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert device.sys_name == "switch1"
end
test "handles connection failure", %{site: site} do
test "handles connection failure", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Unreachable Device",
ip_address: "192.168.1.99",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -277,12 +281,13 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert {:error, :device_unresponsive} = Discovery.discover_device(device)
end
test "handles partial discovery failure gracefully", %{site: site} do
test "handles partial discovery failure gracefully", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Partial Device",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -349,12 +354,13 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert sensors == []
end
test "updates existing device on re-discovery", %{site: site} do
test "updates existing device on re-discovery", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Existing Device",
ip_address: "192.168.1.4",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -415,6 +421,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
end
test "preserves sensor readings when rediscovering device with same sensor_index", %{
organization: organization,
site: site
} do
alias Towerops.Snmp.SensorReading
@ -424,6 +431,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Device With Sensors",
ip_address: "192.168.1.50",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -548,7 +556,10 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert Enum.map(readings, & &1.value) == [42.0, 44.0, 45.0]
end
test "preserves interface stats when rediscovering device with same if_index", %{site: site} do
test "preserves interface stats when rediscovering device with same if_index", %{
organization: organization,
site: site
} do
alias Towerops.Snmp.InterfaceStat
{:ok, device} =
@ -556,6 +567,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Device With Interfaces",
ip_address: "192.168.1.51",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -682,12 +694,13 @@ defmodule Towerops.Snmp.DiscoveryTest do
assert Enum.map(stats, & &1.if_in_octets) == [1_000_000, 2_000_000, 3_000_000]
end
test "deletes sensors that no longer exist during rediscovery", %{site: site} do
test "deletes sensors that no longer exist during rediscovery", %{organization: organization, site: site} do
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Device With Removed Sensor",
ip_address: "192.168.1.52",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -800,6 +813,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Device 1",
ip_address: "192.168.1.10",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -811,6 +825,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Device 2",
ip_address: "192.168.1.11",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -822,6 +837,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Device 3 - No SNMP",
ip_address: "192.168.1.12",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: false
})
@ -858,6 +874,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Good Device",
ip_address: "192.168.1.20",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -869,6 +886,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "Bad Device",
ip_address: "192.168.1.21",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "wrong",
@ -906,7 +924,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
end
test "updates device name from SNMP sysName when device name is empty", %{
organization: _organization,
organization: organization,
site: site
} do
# Create device without a name
@ -915,6 +933,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: nil,
ip_address: "192.168.1.200",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",
@ -960,7 +979,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
end
test "does not update device name from SNMP when device already has a name", %{
organization: _organization,
organization: organization,
site: site
} do
# Create device with a name
@ -969,6 +988,7 @@ defmodule Towerops.Snmp.DiscoveryTest do
name: "My Custom Name",
ip_address: "192.168.1.201",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_community: "public",

View file

@ -27,7 +27,8 @@ defmodule Towerops.Snmp.IpAddressTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -27,7 +27,8 @@ defmodule Towerops.Snmp.MacAddressTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -26,7 +26,8 @@ defmodule Towerops.Snmp.MemoryPoolTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -28,7 +28,8 @@ defmodule Towerops.Snmp.NeighborCleanupWorkerTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device_schema2} =
@ -39,7 +40,8 @@ defmodule Towerops.Snmp.NeighborCleanupWorkerTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
device1 =
@ -216,7 +218,8 @@ defmodule Towerops.Snmp.NeighborCleanupWorkerTest do
name: "No SNMP Device",
ip_address: "192.168.1.100",
snmp_enabled: false,
site_id: site.id
site_id: site.id,
organization_id: org.id
})
device =

View file

@ -27,7 +27,8 @@ defmodule Towerops.Snmp.NeighborTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -26,7 +26,8 @@ defmodule Towerops.Snmp.PhysicalEntityTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -203,7 +203,8 @@ defmodule Towerops.Snmp.ProcessorReadingTest do
Towerops.Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
device

View file

@ -193,7 +193,8 @@ defmodule Towerops.Snmp.StateSensorTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: device_schema.site_id
site_id: device_schema.site_id,
organization_id: device_schema.organization_id
})
snmp_device2 =

View file

@ -26,7 +26,8 @@ defmodule Towerops.Snmp.StorageTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =

View file

@ -26,7 +26,8 @@ defmodule Towerops.Snmp.VlanTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =
@ -184,7 +185,8 @@ defmodule Towerops.Snmp.VlanTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: device_schema.site_id
site_id: device_schema.site_id,
organization_id: device_schema.organization_id
})
snmp_device2 =

View file

@ -33,7 +33,8 @@ defmodule Towerops.SnmpTest do
snmp_version: "2c",
snmp_community: "public",
snmp_port: 161,
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device =
@ -1238,14 +1239,16 @@ defmodule Towerops.SnmpTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
snmp_device1 =
@ -1868,14 +1871,16 @@ defmodule Towerops.SnmpTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, device2} =
Towerops.Devices.create_device(%{
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
old_time = DateTime.add(DateTime.utc_now(), -7200, :second)

View file

@ -33,6 +33,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -83,6 +84,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -133,6 +135,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -182,6 +185,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -231,6 +235,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -289,6 +294,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -342,6 +348,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -399,6 +406,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site1.id,
organization_id: site1.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -409,6 +417,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router 2",
ip_address: "192.168.2.1",
site_id: site2.id,
organization_id: site2.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -464,6 +473,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -519,6 +529,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"
@ -563,6 +574,7 @@ defmodule Towerops.Workers.AgentLatencyEvaluatorTest do
name: "Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
snmp_enabled: true,
snmp_community: "public",
snmp_version: "2c"

View file

@ -223,7 +223,8 @@ defmodule Towerops.Workers.BackupSummaryWorkerTest do
Devices.create_device(%{
name: "Test Device #{System.unique_integer()}",
ip_address: "192.168.1.#{:rand.uniform(254)}",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
device

View file

@ -197,7 +197,8 @@ defmodule Towerops.Workers.BackupTimeoutWorkerTest do
Devices.create_device(%{
name: "Test Device #{System.unique_integer()}",
ip_address: "192.168.1.#{:rand.uniform(254)}",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
device

View file

@ -29,6 +29,7 @@ defmodule Towerops.Workers.DeviceMonitorWorkerTest do
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
monitoring_enabled: true
})
@ -53,6 +54,7 @@ defmodule Towerops.Workers.DeviceMonitorWorkerTest do
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: site.organization_id,
monitoring_enabled: true
})
@ -73,6 +75,7 @@ defmodule Towerops.Workers.DeviceMonitorWorkerTest do
name: "Router 3",
ip_address: "192.168.1.3",
site_id: site.id,
organization_id: site.organization_id,
monitoring_enabled: true
})

View file

@ -29,6 +29,7 @@ defmodule Towerops.Workers.DevicePollerWorkerTest do
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: site.organization_id,
check_interval_seconds: 300
})
@ -53,6 +54,7 @@ defmodule Towerops.Workers.DevicePollerWorkerTest do
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: site.organization_id,
check_interval_seconds: 600
})
@ -74,7 +76,8 @@ defmodule Towerops.Workers.DevicePollerWorkerTest do
Devices.create_device(%{
name: "Router 3",
ip_address: "192.168.1.3",
site_id: site.id
site_id: site.id,
organization_id: site.organization_id
})
# Start polling

View file

@ -73,6 +73,7 @@ defmodule ToweropsWeb.Api.AccountDataControllerTest do
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: org.id,
snmp_enabled: true,
snmp_community: "secret-community-string"
})
@ -111,7 +112,8 @@ defmodule ToweropsWeb.Api.AccountDataControllerTest do
Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Create a recent alert
@ -150,7 +152,8 @@ defmodule ToweropsWeb.Api.AccountDataControllerTest do
Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: org.id
})
# Create an alert with an old triggered_at (120 days ago)

View file

@ -18,7 +18,8 @@ defmodule ToweropsWeb.AlertLive.IndexTest do
Towerops.Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
%{organization: organization, site: site, device: device}

View file

@ -6,7 +6,7 @@ defmodule ToweropsWeb.DashboardLiveTest do
setup :register_and_log_in_user
setup %{user: user} do
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org", use_sites: true}, user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
@ -18,7 +18,16 @@ defmodule ToweropsWeb.DashboardLiveTest do
end
describe "Dashboard" do
test "displays organization name and stats", %{conn: conn, organization: organization} do
test "displays organization name and stats", %{conn: conn, organization: organization, site: site} do
# Create a device so the dashboard shows stats instead of empty state
{:ok, _device} =
Towerops.Devices.create_device(%{
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id
})
{:ok, _view, html} = live(conn, ~p"/orgs/#{organization.slug}")
assert html =~ organization.name
@ -46,6 +55,7 @@ defmodule ToweropsWeb.DashboardLiveTest do
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: true
})
@ -56,6 +66,7 @@ defmodule ToweropsWeb.DashboardLiveTest do
name: "Router 2",
ip_address: "192.168.1.2",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: true
})
@ -72,7 +83,8 @@ defmodule ToweropsWeb.DashboardLiveTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
# Create an active alert
@ -97,7 +109,8 @@ defmodule ToweropsWeb.DashboardLiveTest do
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
{:ok, view, _html} = live(conn, ~p"/orgs/#{organization.slug}")

View file

@ -3,6 +3,8 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
import Phoenix.LiveViewTest
alias Towerops.Snmp.Device
setup :register_and_log_in_user
setup %{user: user} do
@ -21,7 +23,8 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
describe "edit page rendering" do
test "renders edit page when device has no SNMP discovery data (bug fix for nil boolean)", %{
conn: conn,
site: site
site: site,
organization: organization
} do
# Regression test for production bug where visiting edit page for a device
# with no SNMP discovery data would crash with "expected boolean, got nil"
@ -33,6 +36,7 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_port: 161,
@ -52,24 +56,18 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
assert html =~ device.name
end
test "renders MikroTik section for superuser when device is MikroTik", %{
test "renders MikroTik section when device is MikroTik", %{
conn: conn,
site: site,
user: user
organization: organization
} do
alias Towerops.Snmp.Device, as: SnmpDevice
# Make user a superuser using Ecto.Changeset (standard test pattern)
_user =
user
|> Ecto.Changeset.change(%{is_superuser: true})
|> Towerops.Repo.update!()
# Create a device with MikroTik SNMP discovery data
{:ok, device} =
Towerops.Devices.create_device(%{
name: "MikroTik Router",
ip_address: "192.168.1.254",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_port: 161,
@ -79,8 +77,8 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
# Create SNMP device with MikroTik manufacturer (using Towerops.Snmp.Device schema)
_snmp_device =
%SnmpDevice{}
|> SnmpDevice.changeset(%{
%Device{}
|> Device.changeset(%{
device_id: device.id,
manufacturer: "MikroTik",
sys_descr: "RouterOS 7.8",
@ -97,9 +95,54 @@ defmodule ToweropsWeb.DeviceLive.FormTest do
assert html =~ "Edit Device"
assert html =~ device.name
# MikroTik section SHOULD be visible (superuser + MikroTik device)
# MikroTik section SHOULD be visible for all users with MikroTik devices
assert html =~ "MikroTik API Configuration"
end
test "does not render MikroTik section when device is not MikroTik", %{
conn: conn,
site: site,
organization: organization
} do
alias Device, as: SnmpDevice
# Create a device with non-MikroTik SNMP discovery data
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Cisco Switch",
ip_address: "192.168.1.253",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true,
snmp_version: "2c",
snmp_port: 161,
monitoring_enabled: true,
check_interval_seconds: 300
})
# Create SNMP device with Cisco manufacturer
_snmp_device =
%SnmpDevice{}
|> SnmpDevice.changeset(%{
device_id: device.id,
manufacturer: "Cisco Systems",
sys_descr: "Cisco IOS",
sys_object_id: "1.3.6.1.4.1.9",
sys_uptime: 0,
sys_name: "test-switch"
})
|> Towerops.Repo.insert!()
# Visit edit page
{:ok, _view, html} = live(conn, ~p"/devices/#{device.id}/edit")
# Page should render successfully
assert html =~ "Edit Device"
assert html =~ device.name
# MikroTik section SHOULD NOT be visible for non-MikroTik devices
refute html =~ "MikroTik API Configuration"
end
end
describe "non_routable_ip?/1" do

View file

@ -22,7 +22,8 @@ defmodule ToweropsWeb.DeviceLive.ShowTest do
Towerops.Devices.create_device(%{
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id
site_id: site.id,
organization_id: organization.id
})
%{organization: organization, site: site, device: device}

View file

@ -28,6 +28,7 @@ defmodule ToweropsWeb.GraphLive.ShowTest do
name: "Test Router",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
snmp_enabled: true
})

View file

@ -31,6 +31,7 @@ defmodule ToweropsWeb.AccountLive.MyDataTest do
name: "Test Device",
ip_address: "192.168.1.1",
site_id: site.id,
organization_id: organization.id,
monitoring_enabled: true
})