"""Unit tests for docker/build.py command generation.""" import importlib.util from pathlib import Path import sys import pytest _BUILD_PY = Path(__file__).parents[2] / "docker" / "build.py" _spec = importlib.util.spec_from_file_location("docker_build", _BUILD_PY) docker_build = importlib.util.module_from_spec(_spec) _spec.loader.exec_module(docker_build) def _run(capsys: pytest.CaptureFixture[str], *argv: str) -> list[str]: """Run build.py main() in dry-run mode and return the emitted commands.""" full_argv = ["build.py", "--dry-run", *argv] with pytest.MonkeyPatch.context() as mp: mp.setattr(sys, "argv", full_argv) docker_build.main() out = capsys.readouterr().out return [line[2:] for line in out.splitlines() if line.startswith("$ ")] def test_branch_build_pushes_single_ghcr_tag_without_cache_to( capsys: pytest.CaptureFixture[str], ) -> None: commands = _run( capsys, "--tag", "my-branch", "--arch", "amd64", "--build-type", "docker", "--registry", "ghcr", "build", "--push", "--no-cache-to", ) assert len(commands) == 1 cmd = commands[0] # Custom tag -> only the tag itself, no companion "dev"/"latest" tags assert "--tag ghcr.io/esphome/esphome-amd64:my-branch" in cmd assert ":dev" not in cmd # ghcr only -> no Docker Hub image name assert "--tag esphome/esphome-amd64:my-branch" not in cmd # custom tag falls back to the dev cache for reads assert ( "--cache-from type=registry,ref=ghcr.io/esphome/esphome-amd64:cache-dev" in cmd ) assert "--push" in cmd # --no-cache-to must suppress the cache write assert "--cache-to" not in cmd def test_branch_manifest_targets_ghcr_only( capsys: pytest.CaptureFixture[str], ) -> None: commands = _run( capsys, "--tag", "my-branch", "--build-type", "ha-addon", "--registry", "ghcr", "manifest", ) assert commands == [ "docker buildx imagetools create " "--tag ghcr.io/esphome/esphome-hassio:my-branch " "ghcr.io/esphome/esphome-hassio-amd64:my-branch " "ghcr.io/esphome/esphome-hassio-aarch64:my-branch" ] def test_release_build_keeps_both_registries_and_cache_to( capsys: pytest.CaptureFixture[str], ) -> None: commands = _run( capsys, "--tag", "2025.6.0", "--arch", "amd64", "--build-type", "docker", "build", "--push", ) cmd = commands[0] # Default (no --registry) keeps both Docker Hub and ghcr image names assert "--tag esphome/esphome-amd64:2025.6.0" in cmd assert "--tag ghcr.io/esphome/esphome-amd64:2025.6.0" in cmd # Release channel still gets its companion tags assert "--tag esphome/esphome-amd64:latest" in cmd # Without --no-cache-to the cache write is preserved assert ( "--cache-to type=registry,ref=ghcr.io/esphome/esphome-amd64:cache-latest,mode=max" in cmd ) def test_build_no_push_omits_push_and_cache( capsys: pytest.CaptureFixture[str], ) -> None: commands = _run( capsys, "--tag", "my-branch", "--arch", "amd64", "--build-type", "docker", "--registry", "ghcr", "build", ) cmd = commands[0] assert "--tag ghcr.io/esphome/esphome-amd64:my-branch" in cmd assert "--push" not in cmd assert "--cache-to" not in cmd def test_build_dockerhub_only(capsys: pytest.CaptureFixture[str]) -> None: commands = _run( capsys, "--tag", "my-branch", "--arch", "amd64", "--build-type", "docker", "--registry", "dockerhub", "build", "--push", ) cmd = commands[0] assert "--tag esphome/esphome-amd64:my-branch" in cmd assert "ghcr.io" not in cmd # Cache reference falls back to Docker Hub when GHCR isn't selected assert "--cache-from type=registry,ref=esphome/esphome-amd64:cache-dev" in cmd def test_manifest_dockerhub_only(capsys: pytest.CaptureFixture[str]) -> None: commands = _run( capsys, "--tag", "my-branch", "--build-type", "docker", "--registry", "dockerhub", "manifest", ) create = commands[0] assert create.startswith( "docker buildx imagetools create --tag esphome/esphome:my-branch " ) assert "ghcr.io" not in create