towerops/test/towerops/monitoring_test.exs

461 lines
14 KiB
Elixir

defmodule Towerops.MonitoringTest do
use Towerops.DataCase
alias Towerops.Monitoring
describe "monitoring_checks" do
import Towerops.AccountsFixtures
alias Towerops.Monitoring.Check
setup do
user = user_fixture()
{:ok, organization} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)
{:ok, site} =
Towerops.Sites.create_site(%{
name: "Test Site",
organization_id: organization.id
})
{:ok, device} =
Towerops.Devices.create_device(%{
name: "Router 1",
ip_address: "192.168.1.1",
site_id: site.id
})
%{device: device}
end
@valid_attrs %{
status: :success,
response_time_ms: 50,
checked_at: ~U[2025-12-21 12:00:00Z]
}
test "create_check/1 with valid data creates a check", %{device: device} do
attrs = Map.put(@valid_attrs, :device_id, device.id)
assert {:ok, %Check{} = check} = Monitoring.create_check(attrs)
assert check.status == :success
assert check.response_time_ms == 50
end
test "create_check/1 with invalid data returns error changeset" do
assert {:error, %Ecto.Changeset{}} = Monitoring.create_check(%{})
end
test "list_devices_checks/2 returns checks for device", %{device: device} do
attrs = Map.put(@valid_attrs, :device_id, device.id)
{:ok, check} = Monitoring.create_check(attrs)
assert Monitoring.list_devices_checks(device.id) == [check]
end
test "list_devices_checks/2 limits results", %{device: device} do
# Create 10 checks
for i <- 1..10 do
attrs =
@valid_attrs
|> Map.put(:device_id, device.id)
|> Map.put(:checked_at, DateTime.add(~U[2025-12-21 12:00:00Z], i, :second))
{:ok, _check} = Monitoring.create_check(attrs)
end
assert length(Monitoring.list_devices_checks(device.id, 5)) == 5
end
test "get_latest_check/1 returns most recent check", %{device: device} do
attrs1 =
@valid_attrs
|> Map.put(:device_id, device.id)
|> Map.put(:checked_at, ~U[2025-12-21 12:00:00Z])
attrs2 =
@valid_attrs
|> Map.put(:device_id, device.id)
|> Map.put(:checked_at, ~U[2025-12-21 13:00:00Z])
{:ok, _check1} = Monitoring.create_check(attrs1)
{:ok, check2} = Monitoring.create_check(attrs2)
latest = Monitoring.get_latest_check(device.id)
assert latest.id == check2.id
end
test "get_latest_check/1 returns nil when no checks exist", %{device: device} do
assert Monitoring.get_latest_check(device.id) == nil
end
test "delete_old_checks/1 deletes checks older than date", %{device: device} do
old_attrs =
@valid_attrs
|> Map.put(:device_id, device.id)
|> Map.put(:checked_at, ~U[2025-01-01 12:00:00Z])
new_attrs =
@valid_attrs
|> Map.put(:device_id, device.id)
|> Map.put(:checked_at, ~U[2025-12-21 12:00:00Z])
{:ok, _old_check} = Monitoring.create_check(old_attrs)
{:ok, _new_check} = Monitoring.create_check(new_attrs)
{deleted, _} = Monitoring.delete_old_checks(~U[2025-06-01 00:00:00Z])
assert deleted == 1
assert length(Monitoring.list_devices_checks(device.id)) == 1
end
test "get_hourly_stats/3 returns stats for device", %{device: device} do
base_time = ~U[2025-12-21 12:00:00Z]
# Create checks in two different hours
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 50,
checked_at: base_time
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.add(base_time, 30 * 60, :second)
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: DateTime.add(base_time, 3600, :second)
})
start_time = DateTime.add(base_time, -3600, :second)
end_time = DateTime.add(base_time, 7200, :second)
result = Monitoring.get_hourly_stats(device.id, start_time, end_time)
assert %Postgrex.Result{} = result
assert length(result.rows) == 2
# First hour should have 2 successful checks
[first_hour | _] = result.rows
[_bucket, total, successful, failed, avg_response, min_response, max_response, uptime] = first_hour
assert total == 2
assert successful == 2
assert failed == 0
assert avg_response == Decimal.new("75.00")
assert min_response == 50
assert max_response == 100
assert uptime == Decimal.new("100.00")
end
test "get_daily_stats/3 returns stats for device", %{device: device} do
day1 = ~U[2025-12-20 12:00:00Z]
day2 = ~U[2025-12-21 12:00:00Z]
# Create checks on two different days
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 50,
checked_at: day1
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 60,
checked_at: DateTime.add(day1, 3600, :second)
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: day2
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 80,
checked_at: DateTime.add(day2, 3600, :second)
})
start_time = DateTime.add(day1, -24 * 60 * 60, :second)
end_time = DateTime.add(day2, 24 * 60 * 60, :second)
result = Monitoring.get_daily_stats(device.id, start_time, end_time)
assert %Postgrex.Result{} = result
assert length(result.rows) == 2
# First day should have 2 successful checks
[first_day | _] = result.rows
[_bucket, total, successful, failed, avg_response, _min, _max, uptime] = first_day
assert total == 2
assert successful == 2
assert failed == 0
assert avg_response == Decimal.new("55.00")
assert uptime == Decimal.new("100.00")
end
test "get_uptime_percentage/1 returns uptime percentage", %{device: device} do
base_time = DateTime.truncate(DateTime.utc_now(), :second)
# Create 3 successful and 1 failed check (bulk insert for speed)
checks = [
%{
id: Ecto.UUID.generate(),
device_id: device.id,
status: :success,
response_time_ms: 50.0,
checked_at: DateTime.add(base_time, -60, :second),
inserted_at: base_time
},
%{
id: Ecto.UUID.generate(),
device_id: device.id,
status: :success,
response_time_ms: 60.0,
checked_at: DateTime.add(base_time, -120, :second),
inserted_at: base_time
},
%{
id: Ecto.UUID.generate(),
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: DateTime.add(base_time, -180, :second),
inserted_at: base_time
},
%{
id: Ecto.UUID.generate(),
device_id: device.id,
status: :success,
response_time_ms: 70.0,
checked_at: DateTime.add(base_time, -240, :second),
inserted_at: base_time
}
]
Repo.insert_all(Check, checks)
result = Monitoring.get_uptime_percentage(device.id)
assert result == 75.0
end
test "get_uptime_percentage/2 accepts custom days parameter", %{device: device} do
base_time = DateTime.utc_now()
# Create checks within the last 7 days
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.add(base_time, -3 * 24 * 60 * 60, :second)
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 60,
checked_at: DateTime.add(base_time, -5 * 24 * 60 * 60, :second)
})
# Create a check older than 7 days (should not be included)
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: DateTime.add(base_time, -10 * 24 * 60 * 60, :second)
})
result = Monitoring.get_uptime_percentage(device.id, 7)
assert result == 100.0
end
test "get_uptime_percentage/1 returns nil when no checks exist", %{device: device} do
result = Monitoring.get_uptime_percentage(device.id)
assert is_nil(result)
end
test "get_latency_data/2 returns latency checks for graphing", %{device: device} do
base_time = ~U[2025-12-21 12:00:00Z]
# Create successful checks with varying latency
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 50,
checked_at: base_time
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 75,
checked_at: DateTime.add(base_time, 60, :second)
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 100,
checked_at: DateTime.add(base_time, 120, :second)
})
# Create a failed check (should be excluded)
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: DateTime.add(base_time, 180, :second)
})
since = DateTime.add(base_time, -3600, :second)
result = Monitoring.get_latency_data(device.id, since: since, limit: 100)
assert length(result) == 3
# Results should be ordered by checked_at descending
assert Enum.at(result, 0).response_time_ms == 100
assert Enum.at(result, 1).response_time_ms == 75
assert Enum.at(result, 2).response_time_ms == 50
end
test "get_latency_data/2 respects limit option", %{device: device} do
base_time = DateTime.truncate(~U[2025-12-21 12:00:00Z], :second)
# Create 5 checks (bulk insert for speed)
checks =
for i <- 1..5 do
%{
id: Ecto.UUID.generate(),
device_id: device.id,
status: :success,
response_time_ms: (50 + i) * 1.0,
checked_at: DateTime.add(base_time, i * 60, :second),
inserted_at: base_time
}
end
Repo.insert_all(Check, checks)
since = DateTime.add(base_time, -3600, :second)
result = Monitoring.get_latency_data(device.id, since: since, limit: 3)
assert length(result) == 3
end
test "get_latency_data/2 respects since option", %{device: device} do
base_time = ~U[2025-12-21 12:00:00Z]
# Create check before cutoff
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 50,
checked_at: DateTime.add(base_time, -7200, :second)
})
# Create check after cutoff
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :success,
response_time_ms: 75,
checked_at: base_time
})
since = DateTime.add(base_time, -3600, :second)
result = Monitoring.get_latency_data(device.id, since: since, limit: 100)
# Should only include the check after the since timestamp
assert length(result) == 1
assert hd(result).response_time_ms == 75
end
test "get_latency_data/2 returns empty list when no checks exist", %{device: device} do
since = DateTime.add(DateTime.utc_now(), -3600, :second)
result = Monitoring.get_latency_data(device.id, since: since, limit: 100)
assert result == []
end
test "get_latency_data/2 excludes failed checks", %{device: device} do
base_time = ~U[2025-12-21 12:00:00Z]
# Create only failed checks
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: base_time
})
{:ok, _} =
Monitoring.create_check(%{
device_id: device.id,
status: :failure,
response_time_ms: nil,
checked_at: DateTime.add(base_time, 60, :second)
})
since = DateTime.add(base_time, -3600, :second)
result = Monitoring.get_latency_data(device.id, since: since, limit: 100)
assert result == []
end
end
describe "ping" do
alias Towerops.Monitoring.Ping
@tag :integration
test "ping/1 returns ok tuple with response time for localhost" do
# Test with localhost which should always be reachable
case Ping.ping("127.0.0.1", 2000) do
{:ok, response_time} ->
assert is_float(response_time) or is_integer(response_time)
assert response_time >= 0
{:error, _reason} ->
# Ping might fail in some test environments
assert true
end
end
@tag :integration
test "ping/1 returns error tuple for unreachable host" do
# Use a non-routable IP
case Ping.ping("192.0.2.1", 1000) do
{:error, _reason} -> assert true
{:ok, _} -> assert true
end
end
@tag :integration
test "ping/1 with custom timeout" do
result = Ping.ping("127.0.0.1", 5000)
# Result should be either {:ok, time} or {:error, reason}
assert is_tuple(result)
assert elem(result, 0) in [:ok, :error]
end
end
end