refactor: Improve code quality, error handling, and test coverage

- Add file_lock context manager to eliminate duplicate locking patterns
- Add ValidationError, ConfigurationError, CertificateError exceptions
- Improve rollback logic in haproxy_add_servers (track successful ops only)
- Decompose haproxy_add_domain into smaller helper functions
- Consolidate certificate constants (CERTS_DIR, ACME_HOME) to config.py
- Enhance docstrings for internal functions and magic numbers
- Add pytest framework with 48 new tests (269 -> 317 total)
- Increase test coverage from 76% to 86%
  - servers.py: 58% -> 82%
  - certificates.py: 67% -> 86%
  - configuration.py: 69% -> 94%

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
kaffa
2026-02-03 12:50:00 +09:00
parent 18ce812920
commit 6bcfee519c
25 changed files with 6852 additions and 125 deletions

198
tests/unit/test_config.py Normal file
View File

@@ -0,0 +1,198 @@
"""Unit tests for config module."""
import os
import re
from unittest.mock import patch
import pytest
from haproxy_mcp.config import (
DOMAIN_PATTERN,
BACKEND_NAME_PATTERN,
NON_ALNUM_PATTERN,
StatField,
StateField,
POOL_COUNT,
MAX_SLOTS,
MAX_RESPONSE_SIZE,
SOCKET_TIMEOUT,
MAX_BULK_SERVERS,
)
class TestDomainPattern:
"""Tests for DOMAIN_PATTERN regex."""
def test_simple_domain(self):
"""Match simple domain."""
assert DOMAIN_PATTERN.match("example.com") is not None
def test_subdomain(self):
"""Match subdomain."""
assert DOMAIN_PATTERN.match("api.example.com") is not None
def test_deep_subdomain(self):
"""Match deep subdomain."""
assert DOMAIN_PATTERN.match("a.b.c.d.example.com") is not None
def test_hyphenated_domain(self):
"""Match domain with hyphens."""
assert DOMAIN_PATTERN.match("my-api.example-site.com") is not None
def test_numeric_labels(self):
"""Match domain with numeric labels."""
assert DOMAIN_PATTERN.match("api123.example.com") is not None
def test_invalid_starts_with_hyphen(self):
"""Reject domain starting with hyphen."""
assert DOMAIN_PATTERN.match("-example.com") is None
def test_invalid_ends_with_hyphen(self):
"""Reject label ending with hyphen."""
assert DOMAIN_PATTERN.match("example-.com") is None
def test_invalid_underscore(self):
"""Reject domain with underscore."""
assert DOMAIN_PATTERN.match("my_api.example.com") is None
class TestBackendNamePattern:
"""Tests for BACKEND_NAME_PATTERN regex."""
def test_pool_name(self):
"""Match pool backend names."""
assert BACKEND_NAME_PATTERN.match("pool_1") is not None
assert BACKEND_NAME_PATTERN.match("pool_100") is not None
def test_alphanumeric(self):
"""Match alphanumeric names."""
assert BACKEND_NAME_PATTERN.match("backend123") is not None
def test_underscore(self):
"""Match names with underscores."""
assert BACKEND_NAME_PATTERN.match("my_backend") is not None
def test_hyphen(self):
"""Match names with hyphens."""
assert BACKEND_NAME_PATTERN.match("my-backend") is not None
def test_mixed(self):
"""Match mixed character names."""
assert BACKEND_NAME_PATTERN.match("api_example-com_backend") is not None
def test_invalid_dot(self):
"""Reject names with dots."""
assert BACKEND_NAME_PATTERN.match("my.backend") is None
def test_invalid_special_chars(self):
"""Reject names with special characters."""
assert BACKEND_NAME_PATTERN.match("my@backend") is None
assert BACKEND_NAME_PATTERN.match("my/backend") is None
class TestNonAlnumPattern:
"""Tests for NON_ALNUM_PATTERN regex."""
def test_replace_dots(self):
"""Replace dots."""
result = NON_ALNUM_PATTERN.sub("_", "example.com")
assert result == "example_com"
def test_replace_hyphens(self):
"""Replace hyphens."""
result = NON_ALNUM_PATTERN.sub("_", "my-api")
assert result == "my_api"
def test_preserve_alphanumeric(self):
"""Preserve alphanumeric characters."""
result = NON_ALNUM_PATTERN.sub("_", "abc123")
assert result == "abc123"
def test_complex_replacement(self):
"""Complex domain replacement."""
result = NON_ALNUM_PATTERN.sub("_", "api.my-site.example.com")
assert result == "api_my_site_example_com"
class TestStatField:
"""Tests for StatField constants."""
def test_field_indices(self):
"""Verify stat field indices."""
assert StatField.PXNAME == 0
assert StatField.SVNAME == 1
assert StatField.SCUR == 4
assert StatField.SMAX == 6
assert StatField.STATUS == 17
assert StatField.WEIGHT == 18
assert StatField.CHECK_STATUS == 36
class TestStateField:
"""Tests for StateField constants."""
def test_field_indices(self):
"""Verify state field indices."""
assert StateField.BE_ID == 0
assert StateField.BE_NAME == 1
assert StateField.SRV_ID == 2
assert StateField.SRV_NAME == 3
assert StateField.SRV_ADDR == 4
assert StateField.SRV_OP_STATE == 5
assert StateField.SRV_ADMIN_STATE == 6
assert StateField.SRV_PORT == 18
class TestConfigConstants:
"""Tests for configuration constants."""
def test_pool_count(self):
"""Pool count has expected value."""
assert POOL_COUNT == 100
def test_max_slots(self):
"""Max slots has expected value."""
assert MAX_SLOTS == 10
def test_max_response_size(self):
"""Max response size is reasonable."""
assert MAX_RESPONSE_SIZE == 10 * 1024 * 1024 # 10 MB
def test_socket_timeout(self):
"""Socket timeout is reasonable."""
assert SOCKET_TIMEOUT == 5
def test_max_bulk_servers(self):
"""Max bulk servers is reasonable."""
assert MAX_BULK_SERVERS == 10
class TestEnvironmentVariables:
"""Tests for environment variable configuration."""
def test_default_mcp_host(self):
"""Default MCP host is 0.0.0.0."""
# Import fresh to get defaults
with patch.dict(os.environ, {}, clear=True):
# Re-import to test defaults
from importlib import reload
import haproxy_mcp.config as config
reload(config)
# Note: Due to Python's module caching, this test verifies the
# default values are what we expect from the source code
assert config.MCP_HOST == "0.0.0.0"
def test_default_mcp_port(self):
"""Default MCP port is 8000."""
from haproxy_mcp.config import MCP_PORT
assert MCP_PORT == 8000
def test_default_haproxy_host(self):
"""Default HAProxy host is localhost."""
from haproxy_mcp.config import HAPROXY_HOST
assert HAPROXY_HOST == "localhost"
def test_default_haproxy_port(self):
"""Default HAProxy port is 9999."""
from haproxy_mcp.config import HAPROXY_PORT
assert HAPROXY_PORT == 9999