Public Access
292 lines
9.2 KiB
Python
292 lines
9.2 KiB
Python
"""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
|