from pathlib import Path ROOT = Path(__file__).resolve().parents[2] def test_backend_dockerfile_copies_package_sources_before_pip_install() -> None: dockerfile = ROOT / "backend" / "Dockerfile" lines = dockerfile.read_text(encoding="utf-8").splitlines() pip_install_index = lines.index('RUN extras=".[gis]" \\') preceding = "\n".join(lines[:pip_install_index]) assert "COPY pyproject.toml README.md /app/" in preceding assert "COPY app /app/app" in preceding def test_backend_dockerfile_installs_approved_gis_runtime_stack() -> None: dockerfile = (ROOT / "backend" / "Dockerfile").read_text(encoding="utf-8") assert "ARG GEOINTEL_INSTALL_AI=false" in dockerfile assert 'extras=".[gis]"' in dockerfile assert 'extras=".[gis,ai]"' in dockerfile assert "RUN python scripts/gis_import_smoke.py" in dockerfile assert "gdal-bin" in dockerfile assert "libgdal-dev" in dockerfile assert "libgeos-dev" in dockerfile assert "libproj-dev" in dockerfile assert "proj-bin" in dockerfile assert "libxcb1" in dockerfile assert "libgl1" in dockerfile assert "libglib2.0-0" in dockerfile def test_backend_pyproject_exposes_gis_optional_dependency_group() -> None: pyproject = (ROOT / "backend" / "pyproject.toml").read_text(encoding="utf-8") assert "gis = [" in pyproject assert '"rasterio>=1.4.3"' in pyproject assert '"geopandas>=1.0.1"' in pyproject assert '"pyogrio>=0.10.0"' in pyproject assert '"ultralytics>=8.3,<9"' not in pyproject.split("gis = [", 1)[1].split("]", 1)[0] def test_all_in_one_dockerfile_can_opt_into_ai_dependencies_without_base_install() -> None: dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8") assert "ARG GEOINTEL_INSTALL_AI=false" in dockerfile assert 'extras=".[gis]"' in dockerfile assert 'extras=".[gis,ai]"' in dockerfile assert "python scripts/gis_import_smoke.py" in dockerfile assert "yolo_preflight.py" in dockerfile assert "libxcb1" in dockerfile assert "libgl1" in dockerfile assert "libglib2.0-0" in dockerfile def test_all_in_one_dockerfile_copies_operator_scripts_for_runtime_use() -> None: dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8") assert "COPY scripts/prepare_operator_real_data_samples.py /app/scripts/prepare_operator_real_data_samples.py" in dockerfile assert "COPY scripts/export_operator_yolo_tile_dataset.py /app/scripts/export_operator_yolo_tile_dataset.py" in dockerfile assert "COPY scripts/audit_operator_yolo_dataset_quality.py /app/scripts/audit_operator_yolo_dataset_quality.py" in dockerfile assert ( "COPY scripts/render_operator_yolo_label_qa_contact_sheets.py " "/app/scripts/render_operator_yolo_label_qa_contact_sheets.py" ) in dockerfile assert "COPY scripts/train_operator_yolo_detector.sh /app/scripts/train_operator_yolo_detector.sh" in dockerfile def test_compose_does_not_require_missing_root_env_file() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") assert "env_file:" not in compose assert "DATABASE_URL: postgresql+psycopg://${GEOINTEL_POSTGRES_USER:-geointel}" in compose def test_compose_exposes_frontend_on_configurable_host_port_with_cors_origin() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") env_example = (ROOT / ".env.example").read_text(encoding="utf-8") assert '"${GEOINTEL_FRONTEND_PORT:-1202}:80"' in compose assert '"${GEOINTEL_BACKEND_PORT:-8000}:8000"' in compose assert "CORS_ORIGINS: ${GEOINTEL_CORS_ORIGINS:-http://localhost:1202,http://127.0.0.1:1202}" in compose assert "GEOINTEL_FRONTEND_PORT=1202" in env_example assert "GEOINTEL_BACKEND_PORT=8000" in env_example assert "http://localhost:1202" in env_example assert "http://127.0.0.1:1202" in env_example def test_env_example_uses_runtime_env_names_read_by_backend_and_frontend() -> None: env_example = (ROOT / ".env.example").read_text(encoding="utf-8") assert "GEOINTEL_INSTALL_AI=false" in env_example assert "YOLO_ENABLED=false" in env_example assert "YOLO_MODELS_DIR=/app/models" in env_example assert "YOLO_MODEL_PATH=" in env_example assert "YOLO_CONFIG_DIR=./storage/ultralytics" in env_example assert "YOLO_MAX_TILES=100" in env_example assert "YOLO_MAX_DETECTIONS=1000" in env_example assert "YOLO_DUPLICATE_IOU_THRESHOLD=0.5" in env_example assert "ENABLE_YOLO" not in env_example assert "ENABLE_SAM" not in env_example assert "VITE_API_BASE_URL=" in env_example assert "VITE_API_PROXY_TARGET=http://localhost:8000" in env_example def test_frontend_uses_same_origin_api_proxy_by_default() -> None: api_client = (ROOT / "frontend" / "src" / "services" / "api" / "client.ts").read_text(encoding="utf-8") nginx_config = (ROOT / "frontend" / "nginx.conf").read_text(encoding="utf-8") dockerfile = (ROOT / "frontend" / "Dockerfile").read_text(encoding="utf-8") assert '?? ""' in api_client assert "http://localhost:8000" not in api_client assert "FROM nginx:" in dockerfile assert "COPY --from=build /app/dist /usr/share/nginx/html" in dockerfile assert "location /api/" in nginx_config assert "proxy_pass http://backend:8000/api/" in nginx_config assert "location = /health" in nginx_config assert 'add_header Cache-Control "no-cache"' in nginx_config assert "location /assets/" in nginx_config assert "try_files $uri $uri/ /index.html" in nginx_config def test_nginx_runtime_allows_real_gis_upload_payloads() -> None: frontend_nginx = (ROOT / "frontend" / "nginx.conf").read_text(encoding="utf-8") all_in_one_nginx = (ROOT / "deploy" / "unraid" / "nginx-all-in-one.conf").read_text(encoding="utf-8") assert "client_max_body_size 250m;" in frontend_nginx assert "client_max_body_size 250m;" in all_in_one_nginx def test_nginx_runtime_allows_long_ai_and_qa_requests() -> None: frontend_nginx = (ROOT / "frontend" / "nginx.conf").read_text(encoding="utf-8") all_in_one_nginx = (ROOT / "deploy" / "unraid" / "nginx-all-in-one.conf").read_text(encoding="utf-8") for config in (frontend_nginx, all_in_one_nginx): assert "proxy_read_timeout 600s;" in config assert "proxy_send_timeout 600s;" in config def test_compose_does_not_publish_postgis_on_default_host_port() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") assert '"5432:5432"' not in compose def test_compose_waits_for_healthy_database_and_applies_migrations() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") assert "pg_isready -U ${GEOINTEL_POSTGRES_USER:-geointel} -d ${GEOINTEL_POSTGRES_DB:-geointel}" in compose assert "condition: service_healthy" in compose assert "sh /app/docker_start.sh" in compose def test_compose_mounts_demo_fixtures_for_backend_runtime() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") assert "./fixtures:/app/fixtures:ro" in compose def test_compose_has_backend_and_frontend_healthchecks() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") assert "http://127.0.0.1:8000/health" in compose assert "urllib.request.urlopen" in compose assert "http://127.0.0.1/health" in compose assert "wget -q -O -" in compose assert "start_period: 30s" in compose assert "start_period: 10s" in compose def test_frontend_waits_for_healthy_backend_in_compose() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") frontend_section = compose.split(" frontend:", 1)[1] assert "backend:" in frontend_section assert "condition: service_healthy" in frontend_section def test_backend_docker_start_script_waits_for_sql_connection_before_migrations() -> None: script = (ROOT / "backend" / "docker_start.sh").read_text(encoding="utf-8") assert "Waiting for database connection" in script assert "create_engine(settings.database_url" in script assert "SELECT 1" in script assert "python -m alembic upgrade head" in script assert "uvicorn app.main:app --host 0.0.0.0 --port 8000" in script def test_runtime_sets_writable_ultralytics_config_directory() -> None: compose = (ROOT / "docker-compose.yml").read_text(encoding="utf-8") unraid_compose = (ROOT / "docker-compose.unraid.yml").read_text(encoding="utf-8") start_script = (ROOT / "deploy" / "unraid" / "all-in-one-start.sh").read_text(encoding="utf-8") run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") unraid_env = (ROOT / "deploy" / "unraid" / "geointel.env.example").read_text(encoding="utf-8") assert "YOLO_CONFIG_DIR: ${YOLO_CONFIG_DIR:-/app/storage/ultralytics}" in compose assert "YOLO_CONFIG_DIR: ${YOLO_CONFIG_DIR:-/app/storage/ultralytics}" in unraid_compose assert 'export YOLO_CONFIG_DIR="${YOLO_CONFIG_DIR:-$STORAGE_ROOT/ultralytics}"' in start_script assert 'mkdir -p "$PGDATA" "$STORAGE_ROOT" "$YOLO_CONFIG_DIR"' in start_script assert 'YOLO_CONFIG_DIR="${YOLO_CONFIG_DIR:-/app/storage/ultralytics}"' in run_script assert '-e YOLO_CONFIG_DIR="$YOLO_CONFIG_DIR"' in run_script assert "YOLO_CONFIG_DIR=/app/storage/ultralytics" in unraid_env def test_docker_build_contexts_exclude_vendor_build_and_cache_outputs() -> None: required_patterns = { "node_modules", "dist", "__pycache__", "*.pyc", ".pytest_cache", } for relative_path in ("backend/.dockerignore", "frontend/.dockerignore"): content = (ROOT / relative_path).read_text(encoding="utf-8") for pattern in required_patterns: assert pattern in content def test_browser_runtime_verification_script_detects_proxy_contract() -> None: script = (ROOT / "scripts" / "verify_browser_runtime.sh").read_text(encoding="utf-8") assert "/api/v1/projects" in script assert " None: script = (ROOT / "scripts" / "verify_gis_runtime.sh").read_text(encoding="utf-8") assert "/api/v1/system/capabilities" in script assert '"postgis":true' in script assert '"rasterio":true' in script assert '"geopandas":true' in script assert " None: docker_script = (ROOT / "backend" / "scripts" / "gis_import_smoke.py").read_text(encoding="utf-8") root_wrapper = (ROOT / "scripts" / "gis_import_smoke.py").read_text(encoding="utf-8") assert 'REQUIRED_MODULES = ("rasterio", "geopandas", "pyogrio")' in docker_script assert "importlib.import_module" in docker_script assert '"gis_imports"' in docker_script assert 'ROOT / "backend" / "scripts"' in root_wrapper assert "from gis_import_smoke import main" in root_wrapper def test_backend_docker_context_contains_gis_import_smoke_script() -> None: assert (ROOT / "backend" / "scripts" / "gis_import_smoke.py").exists() def test_unraid_deploy_passes_ai_build_arg_and_yolo_runtime_env() -> None: deploy_ps1 = (ROOT / "scripts" / "deploy_tower.ps1").read_text(encoding="utf-8") deploy_sh = (ROOT / "scripts" / "deploy_tower.sh").read_text(encoding="utf-8") run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") assert 'DEPLOY_GEOINTEL_INSTALL_AI="${GEOINTEL_INSTALL_AI:-}"' in deploy_sh assert 'docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI"' in deploy_sh assert 'docker build --build-arg GEOINTEL_INSTALL_AI="$GEOINTEL_INSTALL_AI"' in deploy_ps1 assert "DEPLOY_GEOINTEL_INSTALL_AI" in deploy_ps1 assert "[string]$InstallAi" in deploy_ps1 assert 'YOLO_ENABLED="${YOLO_ENABLED:-false}"' in run_script assert '-e YOLO_ENABLED="$YOLO_ENABLED"' in run_script assert 'YOLO_MODELS_DIR="${YOLO_MODELS_DIR:-/app/models}"' in run_script assert '-e YOLO_MODELS_DIR="$YOLO_MODELS_DIR"' in run_script assert '-e YOLO_MODEL_PATH="$YOLO_MODEL_PATH"' in run_script assert '-e YOLO_MAX_TILES="$YOLO_MAX_TILES"' in run_script assert '-e YOLO_MAX_DETECTIONS="$YOLO_MAX_DETECTIONS"' in run_script assert '-e YOLO_DUPLICATE_IOU_THRESHOLD="$YOLO_DUPLICATE_IOU_THRESHOLD"' in run_script assert "-v \"${GEOINTEL_MODELS_PATH}:/app/models\"" in run_script