from pathlib import Path ROOT = Path(__file__).resolve().parents[2] def test_build_identity_does_not_invalidate_dependency_layers() -> None: dockerfile = (ROOT / "deploy" / "unraid" / "Dockerfile.all-in-one").read_text(encoding="utf-8") dependency_install = dockerfile.index("/usr/bin/python3.11 -m venv /opt/geointel/venv") source_copy = dockerfile.index("COPY backend/ /app/") build_identity = dockerfile.index("ARG GEOINTEL_BUILD_SHA=unknown") assert build_identity > dependency_install assert build_identity > source_copy assert 'org.opencontainers.image.revision="${GEOINTEL_BUILD_SHA}"' in dockerfile assert 'org.opencontainers.image.created="${GEOINTEL_BUILD_TIME}"' in dockerfile assert 'io.geointel.ai.enabled="${GEOINTEL_INSTALL_AI}"' in dockerfile def test_release_deploy_preserves_immutable_and_previous_images() -> None: script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8") assert 'GEOINTEL_RELEASE_VARIANT="ai"' in script assert 'GEOINTEL_RELEASE_VARIANT="gis"' in script assert 'GEOINTEL_RELEASE_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:${GEOINTEL_BUILD_SHA}-${GEOINTEL_RELEASE_VARIANT}"' in script assert 'GEOINTEL_PREVIOUS_IMAGE="${GEOINTEL_IMAGE_REPOSITORY}:previous"' in script assert 'release_image_id="$(' in script assert '[ "$current_image_id" != "$release_image_id" ]' in script assert 'docker tag "$current_image_id" "$GEOINTEL_PREVIOUS_IMAGE"' in script assert "preserving the existing previous image" in script assert 'if docker image inspect "$GEOINTEL_RELEASE_IMAGE"' in script assert "Immutable release tag has conflicting metadata" in script assert "Reusing existing immutable image" in script assert "rollback_previous()" in script assert "Deployed immutable image" in script def test_release_and_container_replacement_are_serialized() -> None: release_script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8") run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") assert "GEOINTEL_DEPLOY_LOCK_FILE" in release_script assert "flock -n 9" in release_script assert "GEOINTEL_CONTAINER_LOCK_FILE" in run_script assert "flock -w 300 8" in run_script assert "GeoIntel container removal did not complete within 60 seconds" in run_script def test_release_waits_for_large_postgis_volume_recovery() -> None: release_script = (ROOT / "deploy" / "unraid" / "deploy-release.sh").read_text(encoding="utf-8") start_script = (ROOT / "deploy" / "unraid" / "all-in-one-start.sh").read_text(encoding="utf-8") assert "for attempt in $(seq 1 480)" in release_script assert "for attempt in $(seq 1 450)" in start_script assert "PostGIS did not become ready within 15 minutes." in start_script assert 'chown postgres:postgres "$PGDATA"' in start_script assert 'chown -R postgres:postgres "$PGDATA"' not in start_script def test_runtime_configuration_is_validated_before_container_replacement() -> None: run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") validation_index = run_script.index("validate_runtime_config") # The script replaces the container with "docker rm -f"; what matters is # that no removal happens before the runtime config has been validated. replacement_index = run_script.index("docker rm -f geointel") assert validation_index < replacement_index assert "known-default PostGIS password" in run_script assert "GEOINTEL_MAX_UPLOAD_MB must be between 1 and 2048" in run_script assert 'docker image inspect "$GEOINTEL_IMAGE"' in run_script def test_fresh_install_smoke_is_isolated_and_cleans_only_its_temp_path() -> None: script = (ROOT / "scripts" / "verify_release_fresh_install.sh").read_text(encoding="utf-8") assert "mktemp -d" in script assert "geointel-fresh-smoke.*" in script assert "-p 127.0.0.1::80" in script assert "GEOINTEL_POSTGRES_PASSWORD=" in script assert "/health/ready" in script assert "/api/v1/system/capabilities" in script assert "docker exec" in script assert "python -m alembic heads" in script def test_manual_rollback_reuses_persistent_paths_and_requires_existing_image() -> None: rollback = (ROOT / "deploy" / "unraid" / "rollback-dockerman-container.sh").read_text(encoding="utf-8") run_script = (ROOT / "deploy" / "unraid" / "run-dockerman-container.sh").read_text(encoding="utf-8") assert "geointel-all-in-one:previous" in rollback assert 'docker image inspect "$GEOINTEL_ROLLBACK_IMAGE"' in rollback assert 'GEOINTEL_IMAGE="$GEOINTEL_ROLLBACK_IMAGE"' in rollback assert '-v "${GEOINTEL_POSTGIS_DATA_PATH}:/var/lib/postgresql/data"' in run_script assert '-v "${GEOINTEL_STORAGE_PATH}:/app/storage"' in run_script def test_readiness_checks_all_release_shell_entrypoints() -> None: readiness = (ROOT / "scripts" / "run_readiness_check.sh").read_text(encoding="utf-8") for path in ( "scripts/deploy_tower.sh", "scripts/verify_release_fresh_install.sh", "scripts/verify_release_upgrade_smoke.sh", "deploy/unraid/all-in-one-start.sh", "deploy/unraid/run-dockerman-container.sh", "deploy/unraid/deploy-release.sh", "deploy/unraid/rollback-dockerman-container.sh", ): assert f"bash -n {path}" in readiness def test_upgrade_smoke_restores_and_upgrades_only_an_isolated_database() -> None: script = (ROOT / "scripts" / "verify_release_upgrade_smoke.sh").read_text(encoding="utf-8") assert "--confirm-isolated-upgrade" in script assert "restore_release_backup_smoke.sh" in script assert "--keep-database" in script assert "geointel_restore_verify_" in script assert "from sqlalchemy import URL" in script assert 'username=os.environ["GEOINTEL_POSTGRES_USER"]' in script assert 'password=os.environ["GEOINTEL_POSTGRES_PASSWORD"]' in script assert 'database=os.environ["TARGET_DB"]' in script assert '"$CONTAINER" sh -c' in script assert '"$CONTAINER" sh -lc' not in script assert "python -m alembic upgrade head" in script assert "production_database_untouched" in script assert 'dropdb --if-exists -U "$db_user" "$TARGET_DB"' in script