| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131 |
- import ast
- import subprocess
- import sys
- from pathlib import Path
- ROOT = Path(__file__).resolve().parents[1]
- ARCH = ROOT / "docs" / "architecture"
- def _route_count() -> int:
- count = 0
- for route_file in (ROOT / "app" / "api").glob("*/*.py"):
- tree = ast.parse(route_file.read_text(encoding="utf-8"))
- for node in ast.walk(tree):
- if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
- continue
- for decorator in node.decorator_list:
- if not isinstance(decorator, ast.Call):
- continue
- function = decorator.func
- if (
- isinstance(function, ast.Attribute)
- and function.attr == "route"
- and isinstance(function.value, ast.Name)
- and function.value.id == "bp"
- ):
- count += 1
- return count
- def test_required_architecture_artifacts_exist():
- required = {
- "ARCHITECTURE_OVERVIEW.md",
- "OPENAPI.yaml",
- "FRONTEND_REMOVAL_INVENTORY.md",
- "DATA_MODEL.md",
- "DEPLOYMENT_SOURCE_OF_TRUTH.md",
- "ADR-001-authentication.md",
- "ADR-002-workflow-engine.md",
- "ADR-003-cross-store-consistency.md",
- "NEXT_ITERATION_ROADMAP.md",
- }
- assert required <= {path.name for path in ARCH.glob("*")}
- def test_openapi_route_inventory_matches_flask_source():
- source_count = _route_count()
- contract = (ARCH / "OPENAPI.yaml").read_text(encoding="utf-8")
- assert source_count > 100
- assert "openapi: 3.1.0" in contract
- assert f"x-route-count: {source_count}" in contract
- assert contract.count("operationId:") == source_count
- def test_openapi_generator_is_reproducible(tmp_path):
- generated = tmp_path / "OPENAPI.yaml"
- subprocess.run(
- [
- sys.executable,
- str(ROOT / "scripts" / "generate_openapi.py"),
- "--output",
- str(generated),
- ],
- cwd=ROOT,
- check=True,
- )
- assert generated.read_bytes() == (ARCH / "OPENAPI.yaml").read_bytes()
- def test_contract_ci_regenerates_and_checks_the_committed_inventory():
- workflow = (ROOT / ".github" / "workflows" / "contracts.yml").read_text(
- encoding="utf-8"
- )
- assert "scripts/generate_openapi.py" in workflow
- assert "git diff --exit-code" in workflow
- assert "tests/test_architecture_artifacts.py" in workflow
- def test_architecture_decisions_capture_product_boundaries():
- combined = "\n".join(
- (ARCH / name).read_text(encoding="utf-8")
- for name in (
- "DATA_MODEL.md",
- "DEPLOYMENT_SOURCE_OF_TRUTH.md",
- "ADR-001-authentication.md",
- "ADR-002-workflow-engine.md",
- "ADR-003-cross-store-consistency.md",
- "NEXT_ITERATION_ROADMAP.md",
- )
- )
- for required_term in (
- "管理员",
- "编辑者",
- "查看者",
- "DataFlow",
- "n8n Workflow",
- "同一环境只允许一个当前生效版本",
- "Qwen",
- "DeepSeek",
- "每日全量一致性巡检",
- "app/",
- "deployment/app/",
- ):
- assert required_term in combined
- def test_removal_inventory_is_bound_to_the_recovery_backup():
- inventory = (ARCH / "FRONTEND_REMOVAL_INVENTORY.md").read_text(
- encoding="utf-8"
- )
- assert "DataOps-platform-backup-before-cleanup-20260716-170321.tar.gz" in inventory
- assert "866c6c4aff127f88497a5ddf39ac8d8da93008cc784ae6f276f80a7cd17d4ef0" in inventory
- assert "348" in inventory
- def test_release_sync_and_package_include_versioned_migrations():
- sync = (ROOT / "deployment" / "sync_release.sh").read_text(encoding="utf-8")
- package = (ROOT / "deployment" / "package_release.sh").read_text(
- encoding="utf-8"
- )
- run_script = (ROOT / "scripts" / "run_dataops.sh").read_text(encoding="utf-8")
- assert 'copy_tree "${ROOT_DIR}/migrations"' in sync
- assert 'copy_file "${ROOT_DIR}/alembic.ini"' in sync
- assert 'copy_into_release "${SCRIPT_DIR}/migrations"' in package
- assert 'copy_into_release "${SCRIPT_DIR}/alembic.ini"' in package
- assert 'alembic" -c "${APP_DIR}/alembic.ini" upgrade head' in run_script
|