Files
dzentra_bot/app/tests/static/test_ci_gate_contract.py

152 lines
4.6 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
import re
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[3]
WORKFLOW_PATH = PROJECT_ROOT / ".gitea" / "workflows" / "ci-required.yml"
DEV_LOCK_PATH = PROJECT_ROOT / "app" / "requirements-dev.lock"
PRODUCTION_LOCK_PATH = PROJECT_ROOT / "app" / "requirements.lock"
CHECKOUT_REFERENCE = (
"https://github.com/actions/checkout"
"@34e114876b0b11c390a56381ad16ebd13914f8d5"
)
EXPECTED_JOB_IDS = (
"quality",
"offline-regression",
"loopback-integration",
)
def _read(path: Path) -> str:
return path.read_text(encoding="utf-8")
def _logical_requirements(lock_text: str) -> tuple[str, ...]:
records: list[str] = []
current: list[str] = []
for raw_line in lock_text.splitlines():
stripped = raw_line.strip()
if not stripped or stripped.startswith("#"):
continue
current.append(stripped.removesuffix("\\").rstrip())
if stripped.endswith("\\"):
continue
records.append(" ".join(current))
current.clear()
assert not current, "Последняя запись lock-файла не завершена"
return tuple(records)
def _locked_versions(lock_text: str) -> dict[str, str]:
versions: dict[str, str] = {}
for record in _logical_requirements(lock_text):
match = re.match(r"^([A-Za-z0-9_.-]+)==([^ ;]+)", record)
assert match is not None, f"Не распознана lock-запись: {record}"
normalized_name = match.group(1).lower().replace("_", "-")
version = match.group(2)
previous = versions.setdefault(normalized_name, version)
assert previous == version, (
f"Для {normalized_name} обнаружены версии {previous} и {version}"
)
return versions
def test_workflow_is_manual_canary_only() -> None:
workflow = _read(WORKFLOW_PATH)
trigger = workflow.split("\non:\n", maxsplit=1)[1].split(
"\nenv:\n",
maxsplit=1,
)[0]
assert trigger.strip() == "workflow_dispatch: {}"
assert "push:" not in trigger
assert "pull_request:" not in trigger
assert "schedule:" not in trigger
assert "workflow_run:" not in trigger
def test_workflow_has_stable_jobs_and_runner_label() -> None:
workflow = _read(WORKFLOW_PATH)
jobs = workflow.split("\njobs:\n", maxsplit=1)[1]
job_ids = tuple(
re.findall(
r"^ ([a-z][a-z0-9-]*):\n name: \1$",
jobs,
flags=re.MULTILINE,
)
)
assert job_ids == EXPECTED_JOB_IDS
assert jobs.count(" runs-on: dzentra-python312\n") == len(
EXPECTED_JOB_IDS
)
def test_workflow_uses_only_immutable_checkout_reference() -> None:
workflow = _read(WORKFLOW_PATH)
action_references = tuple(
re.findall(r"^\s+uses:\s+(\S+)$", workflow, flags=re.MULTILINE)
)
assert action_references == (CHECKOUT_REFERENCE,) * len(EXPECTED_JOB_IDS)
assert workflow.count(" persist-credentials: false\n") == len(
EXPECTED_JOB_IDS
)
def test_workflow_avoids_unsupported_gitea_125_controls() -> None:
workflow = _read(WORKFLOW_PATH)
for key in (
"concurrency",
"continue-on-error",
"environment",
"permissions",
"timeout-minutes",
):
assert re.search(rf"^\s*{re.escape(key)}:", workflow, re.MULTILINE) is None
def test_workflow_is_offline_and_secret_free_after_dependency_install() -> None:
workflow = _read(WORKFLOW_PATH)
assert workflow.count(" --require-hashes \\\n") == len(
EXPECTED_JOB_IDS
)
assert workflow.count(" -r app/requirements-dev.lock\n") == len(
EXPECTED_JOB_IDS
)
assert "secrets." not in workflow
assert "DZENTRA_RUN_LIVE_TESTS" not in workflow
assert "DZENTRA_RUN_POSTGRES_TESTS" not in workflow
assert "docker " not in workflow.lower()
def test_dev_lock_has_hash_for_every_requirement() -> None:
lock_text = _read(DEV_LOCK_PATH)
records = _logical_requirements(lock_text)
assert lock_text.startswith(
"# Сгенерированный hash-lock developer/CI-зависимостей; "
"вручную не редактировать.\n"
)
assert records
for record in records:
assert "==" in record
assert "--hash=sha256:" in record
assert "https://" not in record
assert "git+" not in record
def test_dev_lock_preserves_all_production_versions() -> None:
production_versions = _locked_versions(_read(PRODUCTION_LOCK_PATH))
dev_versions = _locked_versions(_read(DEV_LOCK_PATH))
assert production_versions.items() <= dev_versions.items()