"""Tests for GET /api/v1/trade-in/version (build metadata) — app/core/version.py + app/api/v1/version.py. Isolated FastAPI app (no full app.main import, no DB) — same pattern as tests/test_geocode_reverse_api.py: mount only the router under test. """ from __future__ import annotations import importlib import os os.environ.setdefault("DATABASE_URL", "postgresql+psycopg://test:test@localhost:5432/test") import pytest from fastapi import FastAPI from fastapi.testclient import TestClient from app.api.v1 import version as version_module from app.core import version as version_core @pytest.fixture def app() -> FastAPI: application = FastAPI() application.include_router(version_module.router, prefix="/api/v1/trade-in") return application # ── GET /api/v1/trade-in/version ───────────────────────────────────────────── def test_version_endpoint_shape(app: FastAPI) -> None: client = TestClient(app) r = client.get("/api/v1/trade-in/version") assert r.status_code == 200 body = r.json() assert set(body.keys()) == {"version", "sha", "built_at"} assert isinstance(body["version"], str) and body["version"] assert isinstance(body["sha"], str) and body["sha"] assert isinstance(body["built_at"], str) and body["built_at"] def test_version_endpoint_matches_core_constants(app: FastAPI) -> None: client = TestClient(app) body = client.get("/api/v1/trade-in/version").json() assert body["version"] == version_core.APP_VERSION assert body["sha"] == version_core.BUILD_SHA assert body["built_at"] == version_core.BUILD_DATE def test_version_path_is_public_no_auth_required() -> None: """rbac_guard must let this path through without X-Authenticated-User / session — see app/core/rbac.py::_PUBLIC_PATHS. Not a secret, no DB call.""" from app.core.rbac import _PUBLIC_PATHS assert "/api/v1/trade-in/version" in _PUBLIC_PATHS # ── app/core/version.py — product_version_line / format_build_date_human ──── def test_product_version_line_format() -> None: line = version_core.product_version_line("Мера") assert line.startswith("Мера v") parts = line.split(" · ") assert len(parts) == 3, f"expected 'name vX.Y.Z · sha · date', got {line!r}" def test_format_build_date_human_parses_iso_utc() -> None: assert version_core.format_build_date_human("2026-08-10T12:00:00Z") == "10.08.2026" def test_format_build_date_human_falls_back_on_garbage_without_raising() -> None: assert version_core.format_build_date_human("not-a-date") == "not-a-date" # ── Fallback when APP_VERSION/BUILD_SHA/BUILD_DATE env vars are absent ────── # (local `uvicorn` run without a Docker build — see module docstring in # app/core/version.py). Reloading the module re-executes its module-level # env reads; nothing here may raise. def test_module_import_falls_back_without_build_env(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("APP_VERSION", raising=False) monkeypatch.delenv("BUILD_SHA", raising=False) monkeypatch.delenv("BUILD_DATE", raising=False) reloaded = importlib.reload(version_core) assert reloaded.BUILD_SHA == "dev" assert reloaded.APP_VERSION # non-empty: VERSION file content or "0.0.0" default assert reloaded.BUILD_DATE.endswith("Z") # format/product helpers must still work off the fallback values (no crash). assert reloaded.product_version_line("Мера").startswith("Мера v") # Reload once more so any test running later in this process sees a module # state consistent with whatever env pytest was actually invoked under. importlib.reload(version_core)