165 lines
5.2 KiB
Elixir
165 lines
5.2 KiB
Elixir
defmodule Towerops.Monitoring.PingTest do
|
|
use ExUnit.Case, async: true
|
|
use ExUnitProperties
|
|
|
|
alias Towerops.Monitoring.Ping
|
|
|
|
# These tests focus on IP validation which happens BEFORE any system calls
|
|
describe "IP address validation" do
|
|
test "returns error for invalid IP address" do
|
|
assert {:error, :invalid_ip} = Ping.ping("not-an-ip")
|
|
end
|
|
|
|
test "returns error for empty string" do
|
|
assert {:error, :invalid_ip} = Ping.ping("")
|
|
end
|
|
|
|
test "returns error for malformed IP with out of range octets" do
|
|
assert {:error, :invalid_ip} = Ping.ping("256.256.256.256")
|
|
end
|
|
|
|
test "returns error for partial IP format" do
|
|
# Note: "192.168.1" is valid shorthand (192.168.0.1), so use trailing dot
|
|
assert {:error, :invalid_ip} = Ping.ping("192.168.1.")
|
|
end
|
|
|
|
test "returns error for IP with extra octets" do
|
|
assert {:error, :invalid_ip} = Ping.ping("192.168.1.1.1")
|
|
end
|
|
|
|
test "returns error for IP with letters" do
|
|
assert {:error, :invalid_ip} = Ping.ping("192.168.1.abc")
|
|
end
|
|
|
|
test "returns error for IP with special characters" do
|
|
assert {:error, :invalid_ip} = Ping.ping("192.168.1.1!")
|
|
end
|
|
|
|
test "rejects invalid IPv6 address" do
|
|
assert {:error, :invalid_ip} = Ping.ping("2001:0db8:85a3::8a2e:0370:gggg")
|
|
end
|
|
|
|
test "rejects malformed IPv6 address" do
|
|
assert {:error, :invalid_ip} = Ping.ping(":::1")
|
|
end
|
|
end
|
|
|
|
describe "error handling" do
|
|
test "handles nil IP address with FunctionClauseError" do
|
|
assert_raise FunctionClauseError, fn ->
|
|
Ping.ping(nil)
|
|
end
|
|
end
|
|
|
|
test "handles integer IP address with FunctionClauseError" do
|
|
assert_raise FunctionClauseError, fn ->
|
|
Ping.ping(123)
|
|
end
|
|
end
|
|
|
|
test "handles atom IP address with FunctionClauseError" do
|
|
assert_raise FunctionClauseError, fn ->
|
|
Ping.ping(:localhost)
|
|
end
|
|
end
|
|
|
|
test "handles list IP address with FunctionClauseError" do
|
|
assert_raise FunctionClauseError, fn ->
|
|
Ping.ping([192, 168, 1, 1])
|
|
end
|
|
end
|
|
|
|
test "handles non-integer timeout with FunctionClauseError" do
|
|
assert_raise FunctionClauseError, fn ->
|
|
Ping.ping("127.0.0.1", "1000")
|
|
end
|
|
end
|
|
end
|
|
|
|
describe "PingBehaviour implementation" do
|
|
test "implements ping/1 callback" do
|
|
exports = Ping.module_info(:exports)
|
|
assert {:ping, 1} in exports
|
|
end
|
|
|
|
test "implements ping/2 callback" do
|
|
exports = Ping.module_info(:exports)
|
|
assert {:ping, 2} in exports
|
|
end
|
|
end
|
|
|
|
describe "property-based tests for IP validation" do
|
|
property "invalid IP strings always return error from validation" do
|
|
# Use generators that produce definitely-invalid IPs (no expensive filter)
|
|
check all(
|
|
invalid_ip <-
|
|
one_of([
|
|
# Alphanumeric strings (no dots) are never valid IPs
|
|
string(:alphanumeric, min_length: 1, max_length: 15),
|
|
# IPs with invalid octets (>255)
|
|
map(integer(256..999), &"#{&1}.#{&1}.#{&1}.#{&1}"),
|
|
# IPs with letters in octet position (use ?a..?z range)
|
|
map(string(?a..?z, length: 3), &"192.168.#{&1}.1")
|
|
]),
|
|
max_runs: 10
|
|
) do
|
|
# These fail at validation, before any system call
|
|
result = Ping.ping(invalid_ip)
|
|
assert match?({:error, _}, result)
|
|
end
|
|
end
|
|
|
|
property "valid IPv4 addresses are accepted by inet parser" do
|
|
check all(
|
|
octet1 <- integer(0..255),
|
|
octet2 <- integer(0..255),
|
|
octet3 <- integer(0..255),
|
|
octet4 <- integer(0..255),
|
|
max_runs: 20
|
|
) do
|
|
ip = "#{octet1}.#{octet2}.#{octet3}.#{octet4}"
|
|
# Just verify IP validation passes (don't actually ping)
|
|
assert {:ok, _} = :inet.parse_address(String.to_charlist(ip))
|
|
end
|
|
end
|
|
end
|
|
|
|
# These tests verify the ping output parser behavior
|
|
# by testing the module's internal parsing logic indirectly
|
|
describe "ping output parsing (via mocked system calls)" do
|
|
# We can't easily mock System.cmd, but we can test that the module
|
|
# correctly handles various response formats by testing with valid IPs
|
|
# that return quickly (localhost)
|
|
|
|
@tag :integration
|
|
test "localhost ping returns valid response format" do
|
|
result = Ping.ping("127.0.0.1", 2000)
|
|
|
|
case result do
|
|
{:ok, response_time} ->
|
|
assert is_float(response_time)
|
|
assert response_time >= 0.0
|
|
assert response_time < 2000.0
|
|
|
|
{:error, reason} ->
|
|
# Acceptable errors in test environment
|
|
assert reason in [:timeout, :command_not_found, :parse_error]
|
|
end
|
|
end
|
|
|
|
@tag :integration
|
|
test "IPv6 localhost ping returns valid response format" do
|
|
result = Ping.ping("::1", 2000)
|
|
|
|
case result do
|
|
{:ok, response_time} ->
|
|
assert is_float(response_time)
|
|
assert response_time >= 0.0
|
|
|
|
{:error, reason} ->
|
|
# IPv6 may not be available in all test environments
|
|
assert reason in [:timeout, :command_not_found, :parse_error]
|
|
end
|
|
end
|
|
end
|
|
end
|