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:
Graham McIntire 2026-02-14 10:23:30 -06:00
parent dbed25b366
commit 6d8c3f932b
3 changed files with 298 additions and 0 deletions

View 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

View 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

View 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