refactor(tests): move flat tests/*.py into per-subsystem subfolders
Groups every flat test_*.py under the module it exercises, matching the
existing tests/{profiler,sniffer,prober,collector,correlation,cli,web,
topology,swarm,bus,updater,api,docker,geoip,...} layout. New folders:
services/, fleet/, config/, logging/, db/ (+ db/mysql/), telemetry/,
mutator/, core/.
Path-dependent __file__ references bumped an extra .parent in three
files that moved one level deeper:
- tests/sniffer/test_sniffer_ja3.py (template path)
- tests/services/test_ssh_capture_emit.py (template path)
- tests/cli/test_mode_gating.py (REPO root)
- tests/web/test_env_lazy_jwt.py (repo var)
Also drops two SQLite runtime artifacts (test_decnet.db-{shm,wal}) that
were leaking into the repo from a previous test run.
Fixes two test_service_isolation cases that patched asyncio.sleep (no
longer on the profiler main-loop hot path — same pre-existing bug I
fixed earlier in test_attacker_worker.py) by patching asyncio.wait_for
and passing interval=0.
This commit is contained in:
155
tests/logging/test_logging.py
Normal file
155
tests/logging/test_logging.py
Normal file
@@ -0,0 +1,155 @@
|
||||
"""
|
||||
Tests for DECNET application logging system.
|
||||
|
||||
Covers:
|
||||
- get_logger() factory and _ComponentFilter injection
|
||||
- Rfc5424Formatter component-aware APP-NAME field
|
||||
- Log level gating via DECNET_DEVELOPER
|
||||
- Component tags for all five microservice layers
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
from decnet.logging import _ComponentFilter, get_logger
|
||||
|
||||
# RFC 5424 parser: <PRI>1 TIMESTAMP HOSTNAME APP-NAME PROCID MSGID SD MSG
|
||||
_RFC5424_RE = re.compile(
|
||||
r"^<(\d+)>1 " # PRI
|
||||
r"\S+ " # TIMESTAMP
|
||||
r"\S+ " # HOSTNAME
|
||||
r"(\S+) " # APP-NAME ← what we care about
|
||||
r"\S+ " # PROCID
|
||||
r"(\S+) " # MSGID
|
||||
r"(.+)$", # SD + MSG
|
||||
)
|
||||
|
||||
|
||||
def _format_record(logger: logging.Logger, level: int, msg: str) -> str:
|
||||
"""Emit a log record through the root handler and return the formatted string."""
|
||||
from decnet.config import Rfc5424Formatter
|
||||
formatter = Rfc5424Formatter()
|
||||
record = logger.makeRecord(
|
||||
logger.name, level, "<test>", 0, msg, (), None
|
||||
)
|
||||
# Run all filters attached to the logger so decnet_component gets injected
|
||||
for f in logger.filters:
|
||||
f.filter(record)
|
||||
return formatter.format(record)
|
||||
|
||||
|
||||
class TestGetLogger:
|
||||
def test_returns_logger(self):
|
||||
log = get_logger("cli")
|
||||
assert isinstance(log, logging.Logger)
|
||||
|
||||
def test_logger_name(self):
|
||||
log = get_logger("engine")
|
||||
assert log.name == "decnet.engine"
|
||||
|
||||
def test_filter_attached(self):
|
||||
log = get_logger("api")
|
||||
assert any(isinstance(f, _ComponentFilter) for f in log.filters)
|
||||
|
||||
def test_idempotent_filter(self):
|
||||
log = get_logger("mutator")
|
||||
get_logger("mutator") # second call
|
||||
component_filters = [f for f in log.filters if isinstance(f, _ComponentFilter)]
|
||||
assert len(component_filters) == 1
|
||||
|
||||
@pytest.mark.parametrize("component", ["cli", "engine", "api", "mutator", "collector"])
|
||||
def test_all_components_registered(self, component):
|
||||
log = get_logger(component)
|
||||
assert any(isinstance(f, _ComponentFilter) for f in log.filters)
|
||||
|
||||
|
||||
class TestComponentFilter:
|
||||
def test_injects_attribute(self):
|
||||
f = _ComponentFilter("engine")
|
||||
record = logging.LogRecord("test", logging.INFO, "", 0, "msg", (), None)
|
||||
assert f.filter(record) is True
|
||||
assert record.decnet_component == "engine" # type: ignore[attr-defined]
|
||||
|
||||
def test_always_passes(self):
|
||||
f = _ComponentFilter("collector")
|
||||
record = logging.LogRecord("test", logging.DEBUG, "", 0, "msg", (), None)
|
||||
assert f.filter(record) is True
|
||||
|
||||
|
||||
class TestRfc5424FormatterComponentAware:
|
||||
@pytest.mark.parametrize("component", ["cli", "engine", "api", "mutator", "collector"])
|
||||
def test_app_name_is_component(self, component):
|
||||
log = get_logger(component)
|
||||
line = _format_record(log, logging.INFO, "test message")
|
||||
m = _RFC5424_RE.match(line)
|
||||
assert m is not None, f"Not RFC 5424: {line!r}"
|
||||
assert m.group(2) == component, f"Expected APP-NAME={component!r}, got {m.group(2)!r}"
|
||||
|
||||
def test_fallback_app_name_without_component(self):
|
||||
"""Untagged loggers (no _ComponentFilter) fall back to 'decnet'."""
|
||||
from decnet.config import Rfc5424Formatter
|
||||
formatter = Rfc5424Formatter()
|
||||
record = logging.LogRecord("some.module", logging.INFO, "", 0, "hello", (), None)
|
||||
line = formatter.format(record)
|
||||
m = _RFC5424_RE.match(line)
|
||||
assert m is not None
|
||||
assert m.group(2) == "decnet"
|
||||
|
||||
def test_msgid_is_logger_name(self):
|
||||
log = get_logger("engine")
|
||||
line = _format_record(log, logging.INFO, "deploying")
|
||||
m = _RFC5424_RE.match(line)
|
||||
assert m is not None
|
||||
assert m.group(3) == "decnet.engine"
|
||||
|
||||
|
||||
class TestLogLevelGating:
|
||||
def test_configure_logging_normal_mode_sets_info(self):
|
||||
"""_configure_logging(dev=False) must set root to INFO."""
|
||||
from decnet.config import _configure_logging, Rfc5424Formatter
|
||||
root = logging.getLogger()
|
||||
original_level = root.level
|
||||
original_handlers = root.handlers[:]
|
||||
# Remove any existing RFC5424 handlers so idempotency check doesn't skip
|
||||
root.handlers = [
|
||||
h for h in root.handlers
|
||||
if not (isinstance(h, logging.StreamHandler) and isinstance(h.formatter, Rfc5424Formatter))
|
||||
]
|
||||
try:
|
||||
_configure_logging(dev=False)
|
||||
assert root.level == logging.INFO
|
||||
finally:
|
||||
root.setLevel(original_level)
|
||||
root.handlers = original_handlers
|
||||
|
||||
def test_configure_logging_dev_mode_sets_debug(self):
|
||||
"""_configure_logging(dev=True) must set root to DEBUG."""
|
||||
from decnet.config import _configure_logging, Rfc5424Formatter
|
||||
root = logging.getLogger()
|
||||
original_level = root.level
|
||||
original_handlers = root.handlers[:]
|
||||
root.handlers = [
|
||||
h for h in root.handlers
|
||||
if not (isinstance(h, logging.StreamHandler) and isinstance(h.formatter, Rfc5424Formatter))
|
||||
]
|
||||
try:
|
||||
_configure_logging(dev=True)
|
||||
assert root.level == logging.DEBUG
|
||||
finally:
|
||||
root.setLevel(original_level)
|
||||
root.handlers = original_handlers
|
||||
|
||||
def test_debug_enabled_in_developer_mode(self, monkeypatch):
|
||||
"""Programmatically setting DEBUG on root allows debug records through."""
|
||||
root = logging.getLogger()
|
||||
original_level = root.level
|
||||
root.setLevel(logging.DEBUG)
|
||||
try:
|
||||
assert root.isEnabledFor(logging.DEBUG)
|
||||
finally:
|
||||
root.setLevel(original_level)
|
||||
Reference in New Issue
Block a user