Files
Dokumentace/Python/tests/test_constants.py
T

155 lines
5.7 KiB
Python

"""Tests for the constants module template."""
import re
import sys
from pathlib import Path
import pytest
from src import constants
from src.constants import (
APP_FULL_NAME,
APP_NAME,
APP_TITLE,
DEFAULT_DEBUG,
VERSION,
)
SEMVER = re.compile(r"^\d+\.\d+\.\d+")
def _write_pyproject(tmp_path: Path, version: str = "1.2.3") -> Path:
"""Create a minimal pyproject.toml carrying the given version."""
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text(f'[project]\nname = "demo"\nversion = "{version}"\n', encoding="utf-8")
return pyproject
# ---------------------------------------------------------------------------
# _load_version()
# ---------------------------------------------------------------------------
def test_load_version_reads_pyproject(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""_load_version() takes the version from [project] in pyproject.toml."""
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
monkeypatch.setattr(constants, "_VERSION_FILE", tmp_path / "_version.py")
assert constants._load_version() == "1.2.3"
def test_load_version_writes_fallback_file(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""_load_version() regenerates _version.py so frozen builds keep the version."""
version_file = tmp_path / "_version.py"
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
monkeypatch.setattr(constants, "_VERSION_FILE", version_file)
constants._load_version()
assert '__version__ = "1.2.3"' in version_file.read_text(encoding="utf-8")
def test_load_version_survives_unwritable_fallback(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""An unwritable _version.py location must not break version loading."""
monkeypatch.setattr(constants, "_PYPROJECT", _write_pyproject(tmp_path))
monkeypatch.setattr(constants, "_VERSION_FILE", tmp_path / "missing_dir" / "_version.py")
assert constants._load_version() == "1.2.3"
def test_load_version_falls_back_to_version_module(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""Without pyproject.toml the version comes from src/_version.py."""
from src import _version
monkeypatch.setattr(constants, "_PYPROJECT", tmp_path / "nonexistent.toml")
assert constants._load_version() == _version.__version__
def test_load_version_missing_version_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""A pyproject.toml without [project] version falls through to the next source."""
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text('[project]\nname = "demo"\n', encoding="utf-8")
monkeypatch.setattr(constants, "_PYPROJECT", pyproject)
monkeypatch.setitem(sys.modules, "src._version", None)
assert constants._load_version() == "0.0.0"
def test_load_version_malformed_toml(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""A malformed pyproject.toml falls through instead of raising."""
pyproject = tmp_path / "pyproject.toml"
pyproject.write_text("[project\nversion = ", encoding="utf-8")
monkeypatch.setattr(constants, "_PYPROJECT", pyproject)
monkeypatch.setitem(sys.modules, "src._version", None)
assert constants._load_version() == "0.0.0"
def test_load_version_last_resort(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
"""With no source available at all, _load_version() returns '0.0.0'."""
monkeypatch.setattr(constants, "_PYPROJECT", tmp_path / "nonexistent.toml")
monkeypatch.setitem(sys.modules, "src._version", None)
assert constants._load_version() == "0.0.0"
# ---------------------------------------------------------------------------
# _load_debug()
# ---------------------------------------------------------------------------
@pytest.mark.parametrize("value", ["true", "TRUE", "True", "1", "yes", "YES"])
def test_load_debug_truthy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""_load_debug() accepts 'true', '1' and 'yes' in any casing."""
monkeypatch.setenv("ENV_DEBUG", value)
assert constants._load_debug() is True
@pytest.mark.parametrize("value", ["false", "FALSE", "0", "no", "", "maybe"])
def test_load_debug_falsy_values(value: str, monkeypatch: pytest.MonkeyPatch) -> None:
"""Anything outside the accepted true-values is False."""
monkeypatch.setenv("ENV_DEBUG", value)
assert constants._load_debug() is False
def test_load_debug_defaults_to_false(monkeypatch: pytest.MonkeyPatch) -> None:
"""_load_debug() returns False when ENV_DEBUG is not set."""
monkeypatch.delenv("ENV_DEBUG", raising=False)
assert constants._load_debug() is False
# ---------------------------------------------------------------------------
# Module-level constants
# ---------------------------------------------------------------------------
def test_app_name_is_set() -> None:
"""APP_NAME and APP_FULL_NAME must be filled in per project."""
assert isinstance(APP_NAME, str) and APP_NAME
assert isinstance(APP_FULL_NAME, str) and APP_FULL_NAME
def test_default_debug_is_bool() -> None:
"""DEFAULT_DEBUG should be a bool."""
assert isinstance(DEFAULT_DEBUG, bool)
def test_version_is_prefixed_semver() -> None:
"""VERSION is the project version prefixed with 'v'."""
assert VERSION.startswith("v")
assert SEMVER.match(VERSION.removeprefix("v")), f"Not semver: {VERSION!r}"
def test_version_dev_suffix_matches_debug_flag() -> None:
"""VERSION ends with 'DEV' (no separator) exactly when DEFAULT_DEBUG is True."""
assert VERSION.endswith("DEV") is DEFAULT_DEBUG
def test_app_title_is_full_name_and_version() -> None:
"""APP_TITLE joins APP_FULL_NAME and VERSION with a single space."""
assert APP_TITLE == f"{APP_FULL_NAME} {VERSION}"