test_local_docker_contract.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. from pathlib import Path
  2. ROOT = Path(__file__).resolve().parents[1]
  3. COMPOSE = ROOT / "deploy/docker/docker-compose.yml"
  4. def test_local_compose_defines_the_isolated_stack():
  5. source = COMPOSE.read_text(encoding="utf-8")
  6. for service in (
  7. "postgres:",
  8. "source-postgres:",
  9. "source-mysql:",
  10. "neo4j:",
  11. "minio:",
  12. "minio-init:",
  13. "n8n:",
  14. "backend:",
  15. "frontend:",
  16. ):
  17. assert service in source
  18. assert "name: dataops-test" in source
  19. assert "dataops-test-net" in source
  20. assert "healthcheck:" in source
  21. assert "DATABASE_URL: postgresql://" in source
  22. assert "NEO4J_URI: bolt://neo4j:7687" in source
  23. assert "MINIO_HOST: minio:9000" in source
  24. assert "N8N_API_URL: http://n8n:5678" in source
  25. assert "DATASOURCE_CREDENTIAL_MASTER_KEY:" in source
  26. assert "dataops-test-source-postgres:" in source
  27. assert "dataops-test-source-mysql:" in source
  28. assert "import urllib.request" in source
  29. def test_local_compose_and_n8n_client_have_no_production_fallbacks():
  30. sources = [
  31. COMPOSE.read_text(encoding="utf-8"),
  32. (ROOT / "app/config/config.py").read_text(encoding="utf-8"),
  33. (ROOT / "app/core/data_factory/n8n_client.py").read_text(encoding="utf-8"),
  34. ]
  35. forbidden = (
  36. "n8n.citupro.com",
  37. "company.citupro.com",
  38. "192.168.3.143",
  39. "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9",
  40. )
  41. local_sources = "\n".join(sources)
  42. for value in forbidden:
  43. assert value not in local_sources
  44. def test_active_source_has_no_hardcoded_nonlocal_service_addresses():
  45. source_files = [
  46. *sorted((ROOT / "app").rglob("*.py")),
  47. *sorted((ROOT / "frontend" / "src").rglob("*.js")),
  48. *sorted((ROOT / "frontend" / "src").rglob("*.vue")),
  49. *sorted((ROOT / "frontend").glob(".env.*")),
  50. ]
  51. active_source = "\n".join(
  52. path.read_text(encoding="utf-8") for path in source_files
  53. )
  54. for value in (
  55. "n8n.citupro.com",
  56. "minio.citupro.com",
  57. "192.168.3.143",
  58. "192.168.3.91",
  59. "115.128.14.14",
  60. ):
  61. assert value not in active_source
  62. def test_docker_images_build_from_source_directories_only():
  63. backend = (ROOT / "deploy/docker/backend.Dockerfile").read_text(encoding="utf-8")
  64. frontend = (ROOT / "deploy/docker/frontend.Dockerfile").read_text(encoding="utf-8")
  65. assert "COPY app/" in backend
  66. assert "COPY migrations/" in backend
  67. assert "COPY alembic.ini" in backend
  68. assert "alembic -c alembic.ini upgrade head" in backend
  69. assert "deployment/app" not in backend
  70. assert "gcc" in backend
  71. assert "libc6-dev" in backend
  72. assert "linux-libc-dev" in backend
  73. assert "apt-get purge" in backend
  74. assert "FROM node:24" in frontend
  75. assert "npm ci" in frontend
  76. assert (ROOT / "wsgi.py").is_file()
  77. def test_postgres_bootstrap_reuses_source_ddl_without_reversible_local_user():
  78. bootstrap = (ROOT / "deploy/docker/postgres/init/000-init.sql").read_text(
  79. encoding="utf-8"
  80. )
  81. for ddl in sorted((ROOT / "database").glob("*.sql")):
  82. assert f"/dataops-schema/{ddl.name}" in bootstrap
  83. assert "CREATE DATABASE n8n" in bootstrap
  84. assert "CREATE EXTENSION IF NOT EXISTS vector" in bootstrap
  85. assert not (ROOT / "deploy/docker/postgres/init/010-local-test-user.sql").exists()
  86. readme = (ROOT / "deploy/docker/README.md").read_text(encoding="utf-8")
  87. assert "app.commands.bootstrap_admin" in readme