mirror of
https://github.com/basnijholt/compose-farm.git
synced 2026-02-03 14:13:26 +00:00
350 lines
12 KiB
Python
350 lines
12 KiB
Python
"""Tests for Glances integration."""
|
|
|
|
from pathlib import Path
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
import httpx
|
|
import pytest
|
|
|
|
from compose_farm.config import Config, Host
|
|
from compose_farm.glances import (
|
|
DEFAULT_GLANCES_PORT,
|
|
ContainerStats,
|
|
HostStats,
|
|
fetch_all_container_stats,
|
|
fetch_all_host_stats,
|
|
fetch_container_stats,
|
|
fetch_host_stats,
|
|
)
|
|
|
|
|
|
class TestHostStats:
|
|
"""Tests for HostStats dataclass."""
|
|
|
|
def test_host_stats_creation(self) -> None:
|
|
stats = HostStats(
|
|
host="nas",
|
|
cpu_percent=25.5,
|
|
mem_percent=50.0,
|
|
swap_percent=10.0,
|
|
load=2.5,
|
|
disk_percent=75.0,
|
|
)
|
|
assert stats.host == "nas"
|
|
assert stats.cpu_percent == 25.5
|
|
assert stats.mem_percent == 50.0
|
|
assert stats.disk_percent == 75.0
|
|
assert stats.error is None
|
|
|
|
def test_host_stats_from_error(self) -> None:
|
|
stats = HostStats.from_error("nas", "Connection refused")
|
|
assert stats.host == "nas"
|
|
assert stats.cpu_percent == 0
|
|
assert stats.mem_percent == 0
|
|
assert stats.error == "Connection refused"
|
|
|
|
|
|
class TestFetchHostStats:
|
|
"""Tests for fetch_host_stats function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_host_stats_success(self) -> None:
|
|
quicklook_response = httpx.Response(
|
|
200,
|
|
json={
|
|
"cpu": 25.5,
|
|
"mem": 50.0,
|
|
"swap": 5.0,
|
|
"load": 2.5,
|
|
},
|
|
)
|
|
fs_response = httpx.Response(
|
|
200,
|
|
json=[
|
|
{"mnt_point": "/", "percent": 65.0},
|
|
{"mnt_point": "/mnt/data", "percent": 80.0},
|
|
],
|
|
)
|
|
|
|
async def mock_get(url: str) -> httpx.Response:
|
|
if "quicklook" in url:
|
|
return quicklook_response
|
|
return fs_response
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(side_effect=mock_get)
|
|
|
|
stats = await fetch_host_stats("nas", "192.168.1.6")
|
|
|
|
assert stats.host == "nas"
|
|
assert stats.cpu_percent == 25.5
|
|
assert stats.mem_percent == 50.0
|
|
assert stats.swap_percent == 5.0
|
|
assert stats.load == 2.5
|
|
assert stats.disk_percent == 65.0 # Root filesystem
|
|
assert stats.error is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_host_stats_http_error(self) -> None:
|
|
mock_response = httpx.Response(500)
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(return_value=mock_response)
|
|
|
|
stats = await fetch_host_stats("nas", "192.168.1.6")
|
|
|
|
assert stats.host == "nas"
|
|
assert stats.error == "HTTP 500"
|
|
assert stats.cpu_percent == 0
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_host_stats_timeout(self) -> None:
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(side_effect=httpx.TimeoutException("timeout"))
|
|
|
|
stats = await fetch_host_stats("nas", "192.168.1.6")
|
|
|
|
assert stats.host == "nas"
|
|
assert stats.error == "timeout"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_host_stats_connection_error(self) -> None:
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(
|
|
side_effect=httpx.ConnectError("Connection refused")
|
|
)
|
|
|
|
stats = await fetch_host_stats("nas", "192.168.1.6")
|
|
|
|
assert stats.host == "nas"
|
|
assert stats.error is not None
|
|
assert "Connection refused" in stats.error
|
|
|
|
|
|
class TestFetchAllHostStats:
|
|
"""Tests for fetch_all_host_stats function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_all_host_stats(self) -> None:
|
|
config = Config(
|
|
compose_dir=Path("/opt/compose"),
|
|
hosts={
|
|
"nas": Host(address="192.168.1.6"),
|
|
"nuc": Host(address="192.168.1.2"),
|
|
},
|
|
stacks={"test": "nas"},
|
|
)
|
|
|
|
quicklook_response = httpx.Response(
|
|
200,
|
|
json={
|
|
"cpu": 25.5,
|
|
"mem": 50.0,
|
|
"swap": 5.0,
|
|
"load": 2.5,
|
|
},
|
|
)
|
|
fs_response = httpx.Response(
|
|
200,
|
|
json=[{"mnt_point": "/", "percent": 70.0}],
|
|
)
|
|
|
|
async def mock_get(url: str) -> httpx.Response:
|
|
if "quicklook" in url:
|
|
return quicklook_response
|
|
return fs_response
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(side_effect=mock_get)
|
|
|
|
stats = await fetch_all_host_stats(config)
|
|
|
|
assert "nas" in stats
|
|
assert "nuc" in stats
|
|
assert stats["nas"].cpu_percent == 25.5
|
|
assert stats["nuc"].cpu_percent == 25.5
|
|
assert stats["nas"].disk_percent == 70.0
|
|
|
|
|
|
class TestDefaultPort:
|
|
"""Tests for default Glances port constant."""
|
|
|
|
def test_default_port(self) -> None:
|
|
assert DEFAULT_GLANCES_PORT == 61208
|
|
|
|
|
|
class TestContainerStats:
|
|
"""Tests for ContainerStats dataclass."""
|
|
|
|
def test_container_stats_creation(self) -> None:
|
|
stats = ContainerStats(
|
|
name="nginx",
|
|
host="nas",
|
|
status="running",
|
|
image="nginx:latest",
|
|
cpu_percent=5.5,
|
|
memory_usage=104857600, # 100MB
|
|
memory_limit=1073741824, # 1GB
|
|
memory_percent=9.77,
|
|
network_rx=1000000,
|
|
network_tx=500000,
|
|
uptime="2 hours",
|
|
ports="80->80/tcp",
|
|
engine="docker",
|
|
)
|
|
assert stats.name == "nginx"
|
|
assert stats.host == "nas"
|
|
assert stats.cpu_percent == 5.5
|
|
|
|
|
|
class TestFetchContainerStats:
|
|
"""Tests for fetch_container_stats function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_container_stats_success(self) -> None:
|
|
mock_response = httpx.Response(
|
|
200,
|
|
json=[
|
|
{
|
|
"name": "nginx",
|
|
"status": "running",
|
|
"image": ["nginx:latest"],
|
|
"cpu_percent": 5.5,
|
|
"memory_usage": 104857600,
|
|
"memory_limit": 1073741824,
|
|
"network": {"cumulative_rx": 1000, "cumulative_tx": 500},
|
|
"uptime": "2 hours",
|
|
"ports": "80->80/tcp",
|
|
"engine": "docker",
|
|
},
|
|
{
|
|
"name": "redis",
|
|
"status": "running",
|
|
"image": ["redis:7"],
|
|
"cpu_percent": 1.2,
|
|
"memory_usage": 52428800,
|
|
"memory_limit": 1073741824,
|
|
"network": {},
|
|
"uptime": "3 hours",
|
|
"ports": "",
|
|
"engine": "docker",
|
|
},
|
|
],
|
|
)
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(return_value=mock_response)
|
|
|
|
containers, error = await fetch_container_stats("nas", "192.168.1.6")
|
|
|
|
assert error is None
|
|
assert containers is not None
|
|
assert len(containers) == 2
|
|
assert containers[0].name == "nginx"
|
|
assert containers[0].host == "nas"
|
|
assert containers[0].cpu_percent == 5.5
|
|
assert containers[1].name == "redis"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_container_stats_empty_on_error(self) -> None:
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(side_effect=httpx.TimeoutException("timeout"))
|
|
|
|
containers, error = await fetch_container_stats("nas", "192.168.1.6")
|
|
|
|
assert containers is None
|
|
assert error == "Connection timed out"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_container_stats_handles_string_image(self) -> None:
|
|
"""Test that image field works as string (not just list)."""
|
|
mock_response = httpx.Response(
|
|
200,
|
|
json=[
|
|
{
|
|
"name": "test",
|
|
"status": "running",
|
|
"image": "myimage:v1", # String instead of list
|
|
"cpu_percent": 0,
|
|
"memory_usage": 0,
|
|
"memory_limit": 1,
|
|
"network": {},
|
|
"uptime": "",
|
|
"ports": "",
|
|
"engine": "docker",
|
|
},
|
|
],
|
|
)
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(return_value=mock_response)
|
|
|
|
containers, error = await fetch_container_stats("nas", "192.168.1.6")
|
|
|
|
assert error is None
|
|
assert containers is not None
|
|
assert len(containers) == 1
|
|
assert containers[0].image == "myimage:v1"
|
|
|
|
|
|
class TestFetchAllContainerStats:
|
|
"""Tests for fetch_all_container_stats function."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_fetch_all_container_stats(self) -> None:
|
|
config = Config(
|
|
compose_dir=Path("/opt/compose"),
|
|
hosts={
|
|
"nas": Host(address="192.168.1.6"),
|
|
"nuc": Host(address="192.168.1.2"),
|
|
},
|
|
stacks={"test": "nas"},
|
|
)
|
|
|
|
mock_response = httpx.Response(
|
|
200,
|
|
json=[
|
|
{
|
|
"name": "nginx",
|
|
"status": "running",
|
|
"image": ["nginx:latest"],
|
|
"cpu_percent": 5.5,
|
|
"memory_usage": 104857600,
|
|
"memory_limit": 1073741824,
|
|
"network": {},
|
|
"uptime": "2 hours",
|
|
"ports": "",
|
|
"engine": "docker",
|
|
},
|
|
],
|
|
)
|
|
|
|
with patch("httpx.AsyncClient") as mock_client:
|
|
mock_client.return_value.__aenter__ = AsyncMock(return_value=mock_client.return_value)
|
|
mock_client.return_value.__aexit__ = AsyncMock(return_value=None)
|
|
mock_client.return_value.get = AsyncMock(return_value=mock_response)
|
|
|
|
containers = await fetch_all_container_stats(config)
|
|
|
|
# 2 hosts x 1 container each = 2 containers
|
|
assert len(containers) == 2
|
|
hosts = {c.host for c in containers}
|
|
assert "nas" in hosts
|
|
assert "nuc" in hosts
|