from datetime import UTC, datetime from typing import ClassVar import pytest from app import docker_discovery from app.models import Service, ServiceKind class _FakeImage: tags: ClassVar[list[str]] = ["example/service:1.0"] class _FakeContainer: id = "abcdef1234567890" name = "example-service" status = "running" image = _FakeImage() attrs: ClassVar[dict[str, object]] = { "State": {"Status": "running", "Health": {"Status": "healthy"}}, "Config": {"Image": "example/service:1.0"}, "NetworkSettings": { "Networks": {"default": {}, "monitoring": {}}, "Ports": { "8000/tcp": [{"HostIp": "0.0.0.0", "HostPort": 1253}], "5432/tcp": None, }, }, } class _FakeClient: def __init__(self, **_: object) -> None: self.containers = self self.closed = False def ping(self) -> bool: return True def list(self, all: bool) -> list[_FakeContainer]: assert all is True return [_FakeContainer()] def close(self) -> None: self.closed = True def test_discovery_normalizes_ports_networks_and_health(monkeypatch: pytest.MonkeyPatch) -> None: client = _FakeClient() monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client) result = docker_discovery.discover_containers("tcp://docker.local:2375") assert result.docker_host == "tcp://docker.local:2375" assert result.candidates[0].name == "example-service" assert result.candidates[0].health == "healthy" assert result.candidates[0].networks == ["default", "monitoring"] assert {(port.container_port, port.host_port) for port in result.candidates[0].ports} == { ("8000/tcp", 1253), ("5432/tcp", None), } assert client.closed is True def test_discovery_filters_sensitive_label_keys_but_keeps_discovery_metadata( monkeypatch: pytest.MonkeyPatch, ) -> None: token = "do-not-persist-this-token" labels = { "servicemap.api-token": token, "servicemap.password": "do-not-persist-this-password", "servicemap.name": "Example API", "servicemap.probe": "http", "servicemap.port": "8000", "com.docker.compose.project": "app-stack", "com.docker.compose.service": "web", "com.docker.compose.depends_on": "db", "com.docker.compose.postgres-password": "do-not-persist-this-compose-password", "org.opencontainers.image.title": "Safe image title", "other.secret": "not forwarded", } attrs = { **_FakeContainer.attrs, "Config": {**_FakeContainer.attrs["Config"], "Labels": labels}, } monkeypatch.setattr(_FakeContainer, "attrs", attrs) client = _FakeClient() monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client) result = docker_discovery.discover_containers("tcp://docker.local:2375") candidate = result.candidates[0] serialized = str(candidate.model_dump(mode="json")) assert token not in serialized assert "do-not-persist-this-password" not in serialized assert "do-not-persist-this-compose-password" not in serialized assert "other.secret" not in candidate.labels assert candidate.labels == { "servicemap.name": "Example API", "servicemap.probe": "http", "servicemap.port": "8000", "com.docker.compose.project": "app-stack", "com.docker.compose.service": "web", "com.docker.compose.depends_on": "db", "org.opencontainers.image.title": "Safe image title", } assert candidate.stable_source_ref == "docker:compose:app-stack:web" assert candidate.suggestions[0].kind == ServiceKind.HTTP def test_discovery_rejects_untrusted_scheme() -> None: with pytest.raises(ValueError, match="must use"): docker_discovery.discover_containers("file:///var/run/docker.sock") def test_discovery_marks_imported_and_carries_review_metadata(monkeypatch: pytest.MonkeyPatch) -> None: client = _FakeClient() monkeypatch.setattr(docker_discovery.docker, "DockerClient", lambda **kwargs: client) existing = Service( id="svc-1", name="example-service", kind=ServiceKind.HTTP, url="http://example-service:8000", source_type="docker", source_ref="docker:abcdef123456@example/service:1.0", created_at=datetime.now(UTC), updated_at=datetime.now(UTC), ) result = docker_discovery.discover_containers("tcp://docker.local:2375", [existing]) candidate = result.candidates[0] assert candidate.import_state == "already imported" assert candidate.suggestions[0].source_type == "docker" assert candidate.suggestions[0].source_ref == "docker:abcdef123456@example/service:1.0"