test: add stream_data property-based tests for org settings
This commit is contained in:
parent
95ce9ad06d
commit
dbed25b366
1 changed files with 185 additions and 0 deletions
|
|
@ -1,11 +1,16 @@
|
|||
defmodule ToweropsWeb.Org.SettingsLiveTest do
|
||||
use ToweropsWeb.ConnCase
|
||||
use ExUnitProperties
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Towerops.AccountsFixtures
|
||||
import Towerops.AgentsFixtures
|
||||
import Towerops.OrganizationsFixtures
|
||||
|
||||
alias Towerops.Integrations.Integration
|
||||
alias Towerops.Organizations
|
||||
alias Towerops.Organizations.Organization
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
organization = organization_fixture(user.id)
|
||||
|
|
@ -188,4 +193,184 @@ defmodule ToweropsWeb.Org.SettingsLiveTest do
|
|||
assert html =~ "default organization" or html =~ "already your default"
|
||||
end
|
||||
end
|
||||
|
||||
describe "property-based: organization name validation" do
|
||||
property "changeset validates arbitrary printable names", %{organization: org} do
|
||||
check all name <- string(:printable, max_length: 500), max_runs: 50 do
|
||||
changeset = Organizations.change_organization(org, %{name: name})
|
||||
|
||||
trimmed = String.trim(name)
|
||||
|
||||
if String.length(trimmed) < 2 or String.length(trimmed) > 100 do
|
||||
assert changeset.errors[:name] != nil or not changeset.valid?
|
||||
else
|
||||
# Name within bounds — no name-specific error expected
|
||||
refute Keyword.has_key?(changeset.errors, :name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
property "empty and whitespace-only names are rejected", %{organization: org} do
|
||||
check all spaces <- string([?\s, ?\t, ?\n], min_length: 0, max_length: 20), max_runs: 25 do
|
||||
changeset = Organizations.change_organization(org, %{name: spaces})
|
||||
refute changeset.valid?
|
||||
end
|
||||
end
|
||||
|
||||
property "unicode names within length bounds are accepted", %{organization: org} do
|
||||
check all name <- string(:utf8, min_length: 2, max_length: 100), max_runs: 50 do
|
||||
changeset = Organizations.change_organization(org, %{name: name})
|
||||
# Should not have a name error (length is valid)
|
||||
refute Keyword.has_key?(changeset.errors, :name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "property-based: SNMP config validation" do
|
||||
property "SNMP port validates numeric bounds", %{organization: org} do
|
||||
check all port <- one_of([integer(-1000..70_000), constant(0), constant(nil)]),
|
||||
max_runs: 50 do
|
||||
changeset = Organizations.change_organization(org, %{snmp_port: port})
|
||||
|
||||
if is_integer(port) and port > 0 and port < 65_536 do
|
||||
refute Keyword.has_key?(changeset.errors, :snmp_port)
|
||||
else
|
||||
assert changeset.errors[:snmp_port] != nil or not changeset.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
property "SNMP community string accepts arbitrary strings", %{organization: org} do
|
||||
check all community <- string(:printable, max_length: 200), max_runs: 50 do
|
||||
changeset =
|
||||
Organizations.change_organization(org, %{
|
||||
snmp_version: "2c",
|
||||
snmp_community: community
|
||||
})
|
||||
|
||||
# Community string has no length validation — should not error on community field
|
||||
refute Keyword.has_key?(changeset.errors, :snmp_community)
|
||||
end
|
||||
end
|
||||
|
||||
property "SNMPv3 passwords must be at least 8 chars", %{organization: org} do
|
||||
check all password <- string(:printable, min_length: 0, max_length: 50), max_runs: 50 do
|
||||
changeset =
|
||||
Organizations.change_organization(org, %{
|
||||
snmp_version: "3",
|
||||
snmpv3_username: "testuser",
|
||||
snmpv3_auth_protocol: "SHA-256",
|
||||
snmpv3_auth_password: password,
|
||||
snmpv3_priv_protocol: "AES",
|
||||
snmpv3_priv_password: password
|
||||
})
|
||||
|
||||
if String.length(password) < 8 do
|
||||
# At least one password field should have an error
|
||||
has_error =
|
||||
Keyword.has_key?(changeset.errors, :snmpv3_auth_password) or
|
||||
Keyword.has_key?(changeset.errors, :snmpv3_priv_password)
|
||||
|
||||
assert has_error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
property "SNMP version rejects invalid values", %{organization: org} do
|
||||
check all version <- string(:alphanumeric, min_length: 1, max_length: 10),
|
||||
version not in ["1", "2c", "3"],
|
||||
max_runs: 50 do
|
||||
changeset = Organizations.change_organization(org, %{snmp_version: version})
|
||||
assert Keyword.has_key?(changeset.errors, :snmp_version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "property-based: integration validation" do
|
||||
property "sync_interval_minutes must be positive", %{organization: org} do
|
||||
check all interval <- one_of([integer(-100..200), constant(0), constant(nil)]),
|
||||
max_runs: 50 do
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
organization_id: org.id,
|
||||
provider: "preseem",
|
||||
sync_interval_minutes: interval
|
||||
})
|
||||
|
||||
if is_integer(interval) and interval > 0 do
|
||||
refute Keyword.has_key?(changeset.errors, :sync_interval_minutes)
|
||||
else
|
||||
if is_integer(interval) do
|
||||
assert Keyword.has_key?(changeset.errors, :sync_interval_minutes)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
property "provider must be valid", %{organization: org} do
|
||||
check all provider <- string(:alphanumeric, min_length: 1, max_length: 20),
|
||||
provider not in ["preseem", "gaiia"],
|
||||
max_runs: 50 do
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
organization_id: org.id,
|
||||
provider: provider
|
||||
})
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :provider)
|
||||
end
|
||||
end
|
||||
|
||||
property "API key credential round-trips through changeset" do
|
||||
check all api_key <- string(:printable, min_length: 1, max_length: 200), max_runs: 50 do
|
||||
credentials = %{"api_key" => api_key, "webhook_secret" => "test"}
|
||||
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
provider: "preseem",
|
||||
organization_id: Ecto.UUID.generate(),
|
||||
credentials: credentials
|
||||
})
|
||||
|
||||
applied = Ecto.Changeset.get_change(changeset, :credentials)
|
||||
assert applied["api_key"] == api_key
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "property-based: tab parameter handling" do
|
||||
property "invalid tab params default to general gracefully", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
organization: org
|
||||
} do
|
||||
check all tab <- string(:alphanumeric, min_length: 1, max_length: 50),
|
||||
tab not in ["general", "snmp", "mikrotik", "agents", "integrations"],
|
||||
max_runs: 15 do
|
||||
{:ok, view, html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=#{tab}")
|
||||
|
||||
# Should render without crashing — shows the general tab content
|
||||
assert html =~ "Organization Settings"
|
||||
# The general tab content (org name field) should be visible
|
||||
assert html =~ "Organization Name"
|
||||
# Should not crash or show error page
|
||||
assert render(view) =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
|
||||
property "valid tabs render without error", %{conn: conn, user: user, organization: org} do
|
||||
check all tab <- member_of(["general", "snmp", "agents", "integrations"]),
|
||||
max_runs: 10 do
|
||||
{:ok, _view, html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=#{tab}")
|
||||
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue