- Delete kuma_client.py and all Kuma-related code - Remove /infrastructure/monitors endpoints - Update service start/stop to use Portainer only - Simplify service control widget to show container status - Remove Kuma config and credentials - Delete stale memory tests (moved to core-ai service) - Add test infrastructure with pytest - Add tests for service_groups, config, and health endpoints - Bump version to 1.1.0 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
147 lines
5.1 KiB
Python
147 lines
5.1 KiB
Python
"""Tests for service_groups module."""
|
|
import pytest
|
|
from src.service_groups import (
|
|
ALWAYS_ON_SERVICES,
|
|
SERVICE_GROUPS,
|
|
is_always_on,
|
|
get_service_group,
|
|
get_group_name,
|
|
list_service_groups,
|
|
list_stoppable_services,
|
|
validate_stop_request,
|
|
)
|
|
|
|
|
|
class TestAlwaysOnServices:
|
|
"""Test always-on service configuration."""
|
|
|
|
def test_always_on_services_includes_infrastructure(self):
|
|
"""Critical infrastructure services should be in always-on list."""
|
|
assert "portainer" in ALWAYS_ON_SERVICES
|
|
assert "nginx-proxy-manager" in ALWAYS_ON_SERVICES
|
|
assert "core-api" in ALWAYS_ON_SERVICES
|
|
|
|
def test_uptime_kuma_not_in_always_on(self):
|
|
"""Uptime Kuma was removed from always-on list."""
|
|
assert "uptime-kuma" not in ALWAYS_ON_SERVICES
|
|
|
|
|
|
class TestIsAlwaysOn:
|
|
"""Test is_always_on function."""
|
|
|
|
def test_is_always_on_returns_true_for_infrastructure(self):
|
|
"""Infrastructure services should return True."""
|
|
assert is_always_on("portainer") is True
|
|
assert is_always_on("nginx-proxy-manager") is True
|
|
assert is_always_on("core-api") is True
|
|
|
|
def test_is_always_on_case_insensitive(self):
|
|
"""Function should be case-insensitive."""
|
|
assert is_always_on("PORTAINER") is True
|
|
assert is_always_on("Portainer") is True
|
|
assert is_always_on("PoRtAiNeR") is True
|
|
|
|
def test_is_always_on_returns_false_for_stoppable(self):
|
|
"""Stoppable services should return False."""
|
|
assert is_always_on("jellyfin") is False
|
|
assert is_always_on("nextcloud") is False
|
|
assert is_always_on("unknown-service") is False
|
|
|
|
|
|
class TestGetServiceGroup:
|
|
"""Test get_service_group function."""
|
|
|
|
def test_returns_group_members_for_grouped_service(self):
|
|
"""Should return all services in the group."""
|
|
# Assuming jellyfin is defined in SERVICE_GROUPS
|
|
if "jellyfin" in SERVICE_GROUPS:
|
|
result = get_service_group("jellyfin")
|
|
assert "jellyfin" in result
|
|
|
|
def test_returns_single_item_for_ungrouped_service(self):
|
|
"""Ungrouped services should return themselves."""
|
|
result = get_service_group("some-random-service")
|
|
assert result == ["some-random-service"]
|
|
|
|
def test_returns_copy_not_reference(self):
|
|
"""Should return a copy to prevent modification."""
|
|
if SERVICE_GROUPS:
|
|
group_name = list(SERVICE_GROUPS.keys())[0]
|
|
result1 = get_service_group(group_name)
|
|
result2 = get_service_group(group_name)
|
|
assert result1 is not result2
|
|
|
|
|
|
class TestGetGroupName:
|
|
"""Test get_group_name function."""
|
|
|
|
def test_returns_group_name_for_grouped_service(self):
|
|
"""Should return group name for services in groups."""
|
|
# If ai-stack group exists with ollama
|
|
if "ai-stack" in SERVICE_GROUPS and "ollama" in SERVICE_GROUPS["ai-stack"]:
|
|
assert get_group_name("ollama") == "ai-stack"
|
|
|
|
def test_returns_service_name_for_ungrouped(self):
|
|
"""Ungrouped services should return their own name."""
|
|
assert get_group_name("random-service") == "random-service"
|
|
|
|
|
|
class TestListServiceGroups:
|
|
"""Test list_service_groups function."""
|
|
|
|
def test_returns_all_groups(self):
|
|
"""Should return all defined service groups."""
|
|
result = list_service_groups()
|
|
assert isinstance(result, dict)
|
|
assert result == SERVICE_GROUPS
|
|
|
|
def test_returns_copy(self):
|
|
"""Should return a copy to prevent modification."""
|
|
result = list_service_groups()
|
|
assert result is not SERVICE_GROUPS
|
|
|
|
|
|
class TestListStoppableServices:
|
|
"""Test list_stoppable_services function."""
|
|
|
|
def test_returns_list(self):
|
|
"""Should return a list."""
|
|
result = list_stoppable_services()
|
|
assert isinstance(result, list)
|
|
|
|
def test_excludes_always_on_services(self):
|
|
"""Should not include always-on services."""
|
|
result = list_stoppable_services()
|
|
for service in result:
|
|
assert not is_always_on(service), f"{service} is always-on but in stoppable list"
|
|
|
|
|
|
class TestValidateStopRequest:
|
|
"""Test validate_stop_request function."""
|
|
|
|
def test_valid_for_stoppable_services(self):
|
|
"""Should return valid for stoppable services."""
|
|
stoppable = list_stoppable_services()
|
|
if stoppable:
|
|
is_valid, error = validate_stop_request([stoppable[0]])
|
|
assert is_valid is True
|
|
assert error == ""
|
|
|
|
def test_invalid_for_always_on_services(self):
|
|
"""Should return invalid for always-on services."""
|
|
is_valid, error = validate_stop_request(["portainer"])
|
|
assert is_valid is False
|
|
assert "always-on" in error.lower()
|
|
assert "portainer" in error
|
|
|
|
def test_invalid_if_any_service_is_always_on(self):
|
|
"""Should fail if any service in list is always-on."""
|
|
is_valid, error = validate_stop_request(["jellyfin", "portainer"])
|
|
assert is_valid is False
|
|
|
|
def test_valid_for_empty_list(self):
|
|
"""Empty list should be valid."""
|
|
is_valid, error = validate_stop_request([])
|
|
assert is_valid is True
|
|
assert error == ""
|