test: add stream_data property tests for org settings and integrations
- Organization changeset: name length, SNMP port range, version enum, SNMPv3 fields - Integration changeset: sync interval validation, provider enum - Settings LiveView: tab routing with garbage params, arbitrary name/port/community input - All capped at max_runs: 10-50 for fast execution
This commit is contained in:
parent
dbed25b366
commit
6d8c3f932b
3 changed files with 298 additions and 0 deletions
56
test/towerops/integrations/integration_property_test.exs
Normal file
56
test/towerops/integrations/integration_property_test.exs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
defmodule Towerops.Integrations.IntegrationPropertyTest do
|
||||
@moduledoc "Property-based tests for Integration changeset validation."
|
||||
use Towerops.DataCase
|
||||
use ExUnitProperties
|
||||
|
||||
alias Towerops.Integrations.Integration
|
||||
|
||||
describe "sync_interval_minutes validation" do
|
||||
property "accepts positive integers" do
|
||||
check all interval <- positive_integer(), max_runs: 50 do
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
organization_id: Ecto.UUID.generate(),
|
||||
provider: "preseem",
|
||||
sync_interval_minutes: interval
|
||||
})
|
||||
|
||||
refute Keyword.has_key?(changeset.errors, :sync_interval_minutes)
|
||||
end
|
||||
end
|
||||
|
||||
property "rejects zero and negative values" do
|
||||
check all interval <- integer(-1000..0), max_runs: 30 do
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
organization_id: Ecto.UUID.generate(),
|
||||
provider: "preseem",
|
||||
sync_interval_minutes: interval
|
||||
})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :sync_interval_minutes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "provider validation" do
|
||||
property "rejects arbitrary strings as provider" do
|
||||
valid = MapSet.new(["preseem", "gaiia"])
|
||||
|
||||
check all provider <- string(:alphanumeric, min_length: 1, max_length: 20),
|
||||
not MapSet.member?(valid, provider),
|
||||
max_runs: 50 do
|
||||
changeset =
|
||||
Integration.changeset(%Integration{}, %{
|
||||
organization_id: Ecto.UUID.generate(),
|
||||
provider: provider,
|
||||
sync_interval_minutes: 10
|
||||
})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :provider)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
127
test/towerops/organizations/organization_property_test.exs
Normal file
127
test/towerops/organizations/organization_property_test.exs
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
defmodule Towerops.Organizations.OrganizationPropertyTest do
|
||||
@moduledoc "Property-based tests for Organization changeset validation."
|
||||
use Towerops.DataCase
|
||||
use ExUnitProperties
|
||||
|
||||
alias Towerops.Organizations.Organization
|
||||
|
||||
describe "name validation" do
|
||||
property "accepts valid names (2-100 printable chars)" do
|
||||
check all name <- string(:printable, min_length: 2, max_length: 100), max_runs: 50 do
|
||||
changeset = Organization.changeset(%Organization{}, %{name: name})
|
||||
# Name field itself should be valid (other fields may cause errors)
|
||||
refute Keyword.has_key?(changeset.errors, :name)
|
||||
end
|
||||
end
|
||||
|
||||
property "rejects names shorter than 2 characters" do
|
||||
check all name <- string(:printable, min_length: 0, max_length: 1), max_runs: 25 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{name: name})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :name)
|
||||
end
|
||||
end
|
||||
|
||||
property "rejects names longer than 100 characters" do
|
||||
check all name <- string(:ascii, min_length: 101, max_length: 300), max_runs: 25 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{name: name})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert {:name, _} = List.keyfind(changeset.errors, :name, 0)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "SNMP port validation" do
|
||||
property "accepts valid ports (1-65535)" do
|
||||
check all port <- integer(1..65_535), max_runs: 50 do
|
||||
changeset = Organization.changeset(%Organization{}, %{name: "Test Org", snmp_port: port})
|
||||
refute Keyword.has_key?(changeset.errors, :snmp_port)
|
||||
end
|
||||
end
|
||||
|
||||
property "rejects ports outside valid range" do
|
||||
check all port <- one_of([integer(-1000..0), integer(65_536..100_000)]), max_runs: 50 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{name: "Test Org", snmp_port: port})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :snmp_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "SNMP version validation" do
|
||||
property "rejects arbitrary strings as SNMP version" do
|
||||
valid = MapSet.new(["1", "2c", "3"])
|
||||
|
||||
check all version <- string(:alphanumeric, min_length: 1, max_length: 10),
|
||||
not MapSet.member?(valid, version),
|
||||
max_runs: 50 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{name: "Test Org", snmp_version: version})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :snmp_version)
|
||||
end
|
||||
end
|
||||
|
||||
test "accepts valid SNMP versions" do
|
||||
for version <- ["1", "2c", "3"] do
|
||||
changeset = Organization.changeset(%Organization{}, %{name: "Test Org", snmp_version: version})
|
||||
refute Keyword.has_key?(changeset.errors, :snmp_version)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "SNMPv3 fields" do
|
||||
property "v3 requires username regardless of input" do
|
||||
check all username <- one_of([constant(nil), constant("")]),
|
||||
max_runs: 10 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{
|
||||
name: "Test Org",
|
||||
snmp_version: "3",
|
||||
snmpv3_username: username
|
||||
})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :snmpv3_username)
|
||||
end
|
||||
end
|
||||
|
||||
property "v3 auth password must be at least 8 chars when auth protocol set" do
|
||||
check all password <- string(:ascii, min_length: 1, max_length: 7), max_runs: 30 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{
|
||||
name: "Test Org",
|
||||
snmp_version: "3",
|
||||
snmpv3_username: "testuser",
|
||||
snmpv3_auth_protocol: "SHA-256",
|
||||
snmpv3_auth_password: password
|
||||
})
|
||||
|> Map.put(:action, :insert)
|
||||
|
||||
assert Keyword.has_key?(changeset.errors, :snmpv3_auth_password)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "MikroTik port validation" do
|
||||
property "accepts valid MikroTik ports" do
|
||||
check all port <- integer(1..65_535), max_runs: 50 do
|
||||
changeset =
|
||||
Organization.changeset(%Organization{}, %{
|
||||
name: "Test Org",
|
||||
mikrotik_enabled: true,
|
||||
mikrotik_port: port
|
||||
})
|
||||
|
||||
refute Keyword.has_key?(changeset.errors, :mikrotik_port)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
115
test/towerops_web/live/org/settings_live_property_test.exs
Normal file
115
test/towerops_web/live/org/settings_live_property_test.exs
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
defmodule ToweropsWeb.Org.SettingsLivePropertyTest do
|
||||
@moduledoc "Property-based tests for org settings tab routing and input handling."
|
||||
use ToweropsWeb.ConnCase
|
||||
|
||||
import Phoenix.LiveViewTest
|
||||
import Towerops.AccountsFixtures
|
||||
import Towerops.OrganizationsFixtures
|
||||
|
||||
use ExUnitProperties
|
||||
|
||||
setup do
|
||||
user = user_fixture()
|
||||
organization = organization_fixture(user.id)
|
||||
%{user: user, organization: organization}
|
||||
end
|
||||
|
||||
describe "tab parameter handling" do
|
||||
@valid_tabs ["general", "snmp", "mikrotik", "agents", "integrations"]
|
||||
|
||||
property "invalid tab params default to general tab gracefully", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
organization: org
|
||||
} do
|
||||
check all tab <- string(:alphanumeric, min_length: 1, max_length: 30),
|
||||
tab not in @valid_tabs,
|
||||
max_runs: 10 do
|
||||
{:ok, _view, html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=#{tab}")
|
||||
|
||||
# Should render without crashing — defaults to general tab content
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
|
||||
test "all valid tabs render without error", %{conn: conn, user: user, organization: org} do
|
||||
for tab <- @valid_tabs do
|
||||
{:ok, _view, html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=#{tab}")
|
||||
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "organization name input" do
|
||||
property "changeset validation handles arbitrary name input", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
organization: org
|
||||
} do
|
||||
check all name <- string(:printable, max_length: 200), max_runs: 15 do
|
||||
{:ok, view, _html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings")
|
||||
|
||||
# Should not crash on any printable input
|
||||
html =
|
||||
view
|
||||
|> form("#organization-form", organization: %{name: name})
|
||||
|> render_change()
|
||||
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "SNMP config input" do
|
||||
property "SNMP port handles arbitrary numeric input", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
organization: org
|
||||
} do
|
||||
check all port <- one_of([integer(-100..100_000), constant(0)]), max_runs: 15 do
|
||||
{:ok, view, _html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=snmp")
|
||||
|
||||
# Should not crash
|
||||
html =
|
||||
view
|
||||
|> form("#organization-form", organization: %{snmp_port: port})
|
||||
|> render_change()
|
||||
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
|
||||
property "community string handles arbitrary input", %{
|
||||
conn: conn,
|
||||
user: user,
|
||||
organization: org
|
||||
} do
|
||||
check all community <- string(:printable, max_length: 100), max_runs: 15 do
|
||||
{:ok, view, _html} =
|
||||
conn
|
||||
|> log_in_user(user)
|
||||
|> live(~p"/orgs/#{org.slug}/settings?tab=snmp")
|
||||
|
||||
html =
|
||||
view
|
||||
|> form("#organization-form", organization: %{snmp_community: community})
|
||||
|> render_change()
|
||||
|
||||
assert html =~ "Organization Settings"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue