Skip to content

Commit bee4db5

Browse files
authored
test: add unit tests for config and main modules (#25)
* chore(deps): add pytest and pytest-mock to requirements * test: add pytest configuration and shared test fixtures Add root conftest.py to handle module-level import side effects (sys.argv sanitization, Docker client mocking), pytest.ini with test paths config, and shared fixtures for mock Docker client, config, and logger. * test(config): add unit tests for configuration module Cover flatten_keys, merge_dicts, load_config defaults, environment variable overrides, CLI argument overrides, and init_loggers. * test(main): add unit tests for core logic functions Cover create_docker_client, update_container_cache, is_traefik_running, connect/disconnect_traefik_to_network, and connect_to_all_relevant_networks. * test(main): add unit tests for event monitoring Cover start/stop/die events with and without labels, Traefik start event triggering full reconnection, and non-container event filtering. * ci: add test workflow for pull requests Run pytest on every PR targeting main and on pushes to main. * ci: add minimal permissions to test workflow Restrict GITHUB_TOKEN to read-only contents access as flagged by code scanning.
1 parent 6b75bb4 commit bee4db5

10 files changed

Lines changed: 1158 additions & 0 deletions

File tree

.github/workflows/test.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
name: Tests
2+
3+
on:
4+
pull_request:
5+
branches: [main]
6+
push:
7+
branches: [main]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
test:
14+
runs-on: ubuntu-latest
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Set up Python
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: "3.12"
23+
24+
- name: Install dependencies
25+
run: pip install -r requirements.txt
26+
27+
- name: Run tests
28+
run: pytest tests/ -v --tb=short

conftest.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"""
2+
Root-level conftest.py for pytest.
3+
4+
Patches sys.argv and docker.DockerClient early so that module-level
5+
initialization in config.py and main.py does not fail during test
6+
collection (parse_args reading pytest args, DockerClient connecting
7+
to a real Docker socket).
8+
"""
9+
10+
import sys
11+
from unittest.mock import MagicMock
12+
13+
# config.py calls parse_args() at module level, which reads sys.argv.
14+
# When running under pytest, sys.argv contains pytest args that config.py
15+
# does not recognize, causing a sys.exit(1). We sanitize sys.argv here
16+
# so the initial module import succeeds cleanly.
17+
_original_argv = sys.argv
18+
sys.argv = [sys.argv[0]]
19+
20+
# main.py calls create_docker_client(config) at module level (line 43),
21+
# which instantiates docker.DockerClient and connects to the Docker socket.
22+
# We patch DockerClient before main.py is imported so no real connection
23+
# is attempted during test collection.
24+
import docker # noqa: E402
25+
26+
docker.DockerClient = MagicMock

pytest.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[pytest]
2+
testpaths = tests
3+
python_files = test_*.py
4+
python_classes = Test*
5+
python_functions = test_*

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
docker==7.1.0
22
coloredlogs==15.0.1
33
PyYAML==6.0.1
4+
pytest==8.4.2
5+
pytest-mock==3.14.0

tests/__init__.py

Whitespace-only changes.

tests/conftest.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"""
2+
Shared pytest fixtures for the auto_docker_proxy test suite.
3+
4+
Provides mock Docker client, configuration, and logger fixtures
5+
to isolate tests from real Docker daemon and filesystem dependencies.
6+
"""
7+
8+
import sys
9+
from unittest.mock import MagicMock, patch
10+
11+
import pytest
12+
13+
from config import (
14+
Config,
15+
DockerConfig,
16+
DockerTLSConfig,
17+
LogLevelConfig,
18+
TLSCertificateConfig,
19+
TraefikConfig,
20+
)
21+
22+
23+
def make_test_config(**overrides):
24+
"""
25+
Build a Config NamedTuple with sensible test defaults.
26+
27+
Parameters
28+
----------
29+
overrides : dict
30+
Keyword arguments whose keys match Config field paths.
31+
Currently unused but kept for future extensibility.
32+
33+
Returns
34+
-------
35+
Config
36+
A fully populated Config instance suitable for unit tests.
37+
"""
38+
tls = DockerTLSConfig(
39+
enabled=False,
40+
verify=TLSCertificateConfig(file="/path/to/ca.pem"),
41+
cert=TLSCertificateConfig(file="/path/to/cert.pem"),
42+
key=TLSCertificateConfig(file="/path/to/key.pem"),
43+
)
44+
docker = DockerConfig(host="unix:///var/run/docker.sock", tls=tls)
45+
log_level = LogLevelConfig(general="INFO", application="DEBUG")
46+
traefik = TraefikConfig(
47+
containerName="traefik",
48+
monitoredLabel="^traefik.enable$",
49+
networkLabel="traefik.docker.network",
50+
)
51+
return Config(docker=docker, logLevel=log_level, traefik=traefik)
52+
53+
54+
@pytest.fixture
55+
def mock_docker_client():
56+
"""
57+
Patch ``main.client`` with a MagicMock so no real Docker daemon is needed.
58+
59+
Yields
60+
------
61+
MagicMock
62+
The mock object replacing ``main.client``.
63+
"""
64+
with patch("main.client") as mock_client:
65+
yield mock_client
66+
67+
68+
@pytest.fixture
69+
def mock_config():
70+
"""
71+
Patch ``main.config`` with a realistic test Config NamedTuple.
72+
73+
Yields
74+
------
75+
Config
76+
The test configuration injected into ``main.config``.
77+
"""
78+
cfg = make_test_config()
79+
with patch("main.config", cfg):
80+
yield cfg
81+
82+
83+
@pytest.fixture
84+
def mock_logger():
85+
"""
86+
Patch ``main.app_logger`` with a MagicMock to capture log calls.
87+
88+
Yields
89+
------
90+
MagicMock
91+
The mock object replacing ``main.app_logger``.
92+
"""
93+
with patch("main.app_logger") as mock_log:
94+
yield mock_log

tests/unit/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)