homelab-codex-ws/services/stability-agent/tests/test_container_service_name.py
oskar f92e161ec6 fix(stability-agent): tag containers_not_running events with compose service
The aggregate containers_not_running event carried service=None, which the
observer skips when building service state and incidents — stability-agent's
flagship signal never opened an incident (recon D15). Emit one event per
non-running container instead, tagged with the compose service name from the
com.docker.compose.service label (same pattern as node-agent's
_canonical_container_name fix from May), falling back to the container name
with Docker's stale-state hash prefix stripped; never crashes on unlabeled
containers. 'created' compose tracking artifacts are skipped — they are not
running services and would open fake incidents now that the event is
actionable.

Adds the service's first test suite covering the label-extraction helper.
Smoke-run performed with runtime paths redirected (no docker build, authoring
only): main loop runs, service names resolve on live solaria containers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 19:22:07 +02:00

48 lines
1.7 KiB
Python

"""Tests for container_service_name — the compose-label extraction that fixes
the service=None containers_not_running events (recon D15: observer skips
events without a service, so stability-agent's flagship signal never opened
an incident). Input shape is a /containers/json (Docker list API) entry.
"""
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
from stability_agent import container_service_name
def test_compose_label_wins_over_container_name():
c = {
"Names": ["/kb-postgres-db-1"],
"Labels": {"com.docker.compose.service": "kb-postgres"},
}
assert container_service_name(c) == "kb-postgres"
def test_unlabeled_falls_back_to_container_name():
assert container_service_name({"Names": ["/paperless"], "Labels": {}}) == "paperless"
def test_missing_labels_key_does_not_crash():
assert container_service_name({"Names": ["/mosquitto"]}) == "mosquitto"
def test_null_labels_does_not_crash():
assert container_service_name({"Names": ["/mosquitto"], "Labels": None}) == "mosquitto"
def test_blank_label_falls_back_to_name():
c = {"Names": ["/zigbee2mqtt"], "Labels": {"com.docker.compose.service": " "}}
assert container_service_name(c) == "zigbee2mqtt"
def test_stale_state_hash_prefix_stripped():
# Docker stores stale project-state records as "<12-hex>_<original-name>";
# same ghost-key corruption node-agent's _canonical_container_name handles.
c = {"Names": ["/9e36297651e7_control-plane-observer"], "Labels": {}}
assert container_service_name(c) == "control-plane-observer"
def test_empty_container_dict_degrades_to_unknown():
assert container_service_name({}) == "unknown"