"""Unit tests for devx.tools.check_docker_init.""" from __future__ import annotations import textwrap from pathlib import Path from click.testing import CliRunner from devx.tools.check_docker_init import _check_template, _find_compose_templates, _parse_services, main class TestFindComposeTemplates: def test_finds_docker_compose_templates(self, tmp_path: Path): (tmp_path / "docker-compose.observability.yml.j2").write_text("services:") (tmp_path / "docker-compose.service.yml.j2").write_text("services:") result = _find_compose_templates(tmp_path) assert len(result) == 2 def test_finds_exporters_compose(self, tmp_path: Path): (tmp_path / "exporters-compose.yml.j2").write_text("services:") result = _find_compose_templates(tmp_path) assert len(result) == 1 assert "exporters-compose" in str(result[0]) def test_finds_compose_yaml_templates(self, tmp_path: Path): (tmp_path / "compose.yaml.j2").write_text("services:") result = _find_compose_templates(tmp_path) assert len(result) == 1 def test_single_file(self, tmp_path: Path): f = tmp_path / "docker-compose.test.yml.j2" f.write_text("services:") result = _find_compose_templates(f) assert result == [f] def test_nonexistent_path(self, tmp_path: Path): assert _find_compose_templates(tmp_path / "nonexistent") == [] def test_deduplicates(self, tmp_path: Path): (tmp_path / "docker-compose.yml.j2").write_text("services:") result = _find_compose_templates(tmp_path) assert len(result) == 1 def test_recursive(self, tmp_path: Path): (tmp_path / "sub").mkdir() (tmp_path / "sub" / "docker-compose.yml.j2").write_text("services:") result = _find_compose_templates(tmp_path) assert len(result) == 1 class TestParseServices: def test_basic_services(self): content = textwrap.dedent(""" services: web: image: nginx healthcheck: test: ["CMD", "curl", "localhost"] db: image: postgres networks: default: """).strip() services = _parse_services(content) assert "web" in services assert "db" in services assert any("image: nginx" in line for line in services["web"]) def test_jinja2_service_names(self): content = textwrap.dedent(""" services: {{ app_name }}: image: {{ app_image }} healthcheck: test: ["CMD", "curl"] {{ app_name }}-db: image: postgres networks: traefik: """).strip() services = _parse_services(content) assert "{{ app_name }}" in services assert "{{ app_name }}-db" in services def test_no_services_section(self): content = "version: '3'\nvolumes:\n data:" assert _parse_services(content) == {} def test_service_at_end_of_file(self): content = textwrap.dedent(""" services: web: image: nginx """).strip() services = _parse_services(content) assert "web" in services def test_volumes_ends_services(self): content = textwrap.dedent(""" services: web: image: nginx volumes: data: """).strip() services = _parse_services(content) assert "web" in services assert "data" not in services class TestCheckTemplate: def test_service_with_healthcheck_and_init_ok(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx init: true healthcheck: test: ["CMD", "curl", "localhost"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) assert _check_template(f, tmp_path) == [] def test_service_with_healthcheck_no_init_flagged(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx healthcheck: test: ["CMD", "curl", "localhost"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) errors = _check_template(f, tmp_path) assert len(errors) == 1 assert "web" in errors[0] assert "init: true" in errors[0] def test_service_without_healthcheck_ok(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) assert _check_template(f, tmp_path) == [] def test_multiple_services_some_missing(self, tmp_path: Path): content = textwrap.dedent(""" services: good: image: nginx init: true healthcheck: test: ["CMD", "curl"] bad: image: redis healthcheck: test: ["CMD", "redis-cli", "ping"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) errors = _check_template(f, tmp_path) assert len(errors) == 1 assert "bad" in errors[0] assert "good" not in errors[0] def test_no_services_section(self, tmp_path: Path): content = "version: '3'\nvolumes:\n data:" f = tmp_path / "docker-compose.yml.j2" f.write_text(content) assert _check_template(f, tmp_path) == [] def test_jinja2_conditional_service(self, tmp_path: Path): content = textwrap.dedent(""" services: {% if backup_enabled %} backup: image: backup healthcheck: test: ["CMD-SHELL", "pgrep backup"] {% endif %} networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) errors = _check_template(f, tmp_path) assert len(errors) == 1 assert "backup" in errors[0] def test_relative_path_in_error(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx healthcheck: test: ["CMD"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) errors = _check_template(f, tmp_path) assert len(errors) == 1 assert "docker-compose.yml.j2" in errors[0] assert str(tmp_path) not in errors[0] class TestMain: def test_passes_when_all_ok(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx init: true healthcheck: test: ["CMD"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) runner = CliRunner() result = runner.invoke(main, ["--path", str(f)]) assert result.exit_code == 0 assert "OK" in result.output def test_fails_when_missing_init(self, tmp_path: Path): content = textwrap.dedent(""" services: web: image: nginx healthcheck: test: ["CMD"] networks: default: """).strip() f = tmp_path / "docker-compose.yml.j2" f.write_text(content) runner = CliRunner() result = runner.invoke(main, ["--path", str(f)]) assert result.exit_code == 1 assert "FAIL" in result.output assert "web" in result.output def test_default_dir(self, tmp_path: Path): (tmp_path / "docker-compose.good.yml.j2").write_text( textwrap.dedent(""" services: web: image: nginx init: true healthcheck: test: ["CMD"] networks: default: """).strip() ) (tmp_path / "docker-compose.bad.yml.j2").write_text( textwrap.dedent(""" services: db: image: postgres healthcheck: test: ["CMD"] networks: default: """).strip() ) runner = CliRunner() result = runner.invoke(main, ["--templates-dir", str(tmp_path)]) assert result.exit_code == 1 assert "db" in result.output def test_no_templates_found(self, tmp_path: Path): runner = CliRunner() result = runner.invoke(main, ["--templates-dir", str(tmp_path)]) assert result.exit_code == 0 assert "OK" in result.output