"""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>_"; # 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"