Skip to content

Commit 02f5572

Browse files
committed
refactor(core): handle empty auth config and tighten registry mocks
1 parent 34a2499 commit 02f5572

4 files changed

Lines changed: 29 additions & 6 deletions

File tree

core/testcontainers/core/auth.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ def process_docker_auth_config_encoded(auth_config_dict: dict[str, dict[str, dic
3131

3232
auths = auth_config_dict.get("auths")
3333
if not auths:
34-
raise KeyError("No auths found in the docker auth config")
34+
# An explicit but empty ``auths`` map means "no credentials configured".
35+
# Treat it the same as a missing key so callers don't fail to start.
36+
return auth_info
3537

3638
for registry, auth in auths.items():
3739
auth_str = str(auth.get("auth"))

core/tests/registry_mocks.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from typing import Iterator, Optional
1919
from unittest.mock import MagicMock, patch
2020

21+
import docker
2122
from docker.errors import ImageNotFound, NotFound
2223

2324
from testcontainers.core.docker_client import DockerClient
@@ -29,6 +30,21 @@ def make_auth_config(registry: str, username: str, password: str) -> str:
2930
return json.dumps({"auths": {registry: {"auth": creds}}})
3031

3132

33+
def _build_mock_docker_client() -> MagicMock:
34+
"""Construct a Docker SDK mock constrained to the surface we actually use.
35+
36+
Using ``spec=docker.DockerClient`` keeps the mock honest: production code
37+
that calls a non-existent SDK method will raise ``AttributeError`` rather
38+
than silently returning a new ``MagicMock``.
39+
"""
40+
mock_client = MagicMock(spec=docker.DockerClient)
41+
# ``DockerClient.__init__`` writes session headers via ``client.api.headers``.
42+
# ``api`` isn't on the public ``DockerClient`` spec, so attach it explicitly.
43+
mock_client.api = MagicMock()
44+
mock_client.api.headers = {}
45+
return mock_client
46+
47+
3248
@contextmanager
3349
def mock_docker_registry(
3450
*,
@@ -51,7 +67,7 @@ def mock_docker_registry(
5167
:func:`make_auth_config`). When provided, it is applied for the duration
5268
of the context so ``DockerClient`` performs a registry login on init.
5369
"""
54-
mock_client = MagicMock()
70+
mock_client = _build_mock_docker_client()
5571
if missing_image:
5672
mock_client.images.get.side_effect = ImageNotFound("missing")
5773
mock_client.images.pull.side_effect = NotFound("missing")

core/tests/test_auth.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,8 @@ def test_parse_docker_auth_config_error():
6969

7070

7171
def test_parse_docker_auth_config_empty_auths():
72-
auth_config_str = '{"auths": {}}'
73-
with pytest.raises(ValueError):
74-
parse_docker_auth_config(auth_config_str)
72+
"""An explicit but empty ``auths`` map means 'no credentials configured'."""
73+
assert parse_docker_auth_config('{"auths": {}}') == []
7574

7675

7776
def test_parse_docker_auth_all():

core/tests/test_core_registry.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,15 @@ def test_missing_on_private_registry_raises_not_found(image, tag, username, pass
5555
registry_url = "localhost:5000"
5656
auth_config = make_auth_config(registry_url, username, password)
5757

58-
with mock_docker_registry(missing_image=True, auth_config=auth_config):
58+
with mock_docker_registry(missing_image=True, auth_config=auth_config) as mock_client:
5959
client = DockerClient()
6060
with pytest.raises(NotFound):
6161
client.create(f"{registry_url}/{image}:{tag}")
6262

63+
full_image = f"{registry_url}/{image}:{tag}"
64+
mock_client.images.get.assert_called_once_with(full_image)
65+
mock_client.images.pull.assert_called_once_with(full_image)
66+
6367

6468
@pytest.mark.parametrize(
6569
"image,tag,username,password",
@@ -79,6 +83,8 @@ def test_pull_from_private_registry_creates_container(image, tag, username, pass
7983
client = DockerClient()
8084
container = client.create(full_image)
8185

86+
# Auth config was honored at client init.
87+
mock_client.login.assert_called_once_with(registry=registry_url, username=username, password=password)
8288
# Image was not cached locally, so it must be pulled from the registry...
8389
mock_client.images.pull.assert_called_once_with(full_image)
8490
# ...and the container is then created from it.

0 commit comments

Comments
 (0)