import zipfile from unittest.mock import patch import pytest from services import geopolitics, updater @pytest.fixture(autouse=True) def _clear_gdelt_caches(): geopolitics._article_title_cache.clear() geopolitics._article_url_safety_cache.clear() yield geopolitics._article_title_cache.clear() geopolitics._article_url_safety_cache.clear() class TestGdeltArticleUrlSafety: def test_safe_public_article_url_allows_public_dns(self, monkeypatch): monkeypatch.setattr( geopolitics.socket, "getaddrinfo", lambda *args, **kwargs: [(0, 0, 0, "", ("93.184.216.34", 443))], ) allowed, reason = geopolitics._is_safe_public_article_url("https://example.com/story") assert allowed is True assert reason == "" def test_safe_public_article_url_blocks_private_dns(self, monkeypatch): monkeypatch.setattr( geopolitics.socket, "getaddrinfo", lambda *args, **kwargs: [(0, 0, 0, "", ("10.0.0.7", 443))], ) allowed, reason = geopolitics._is_safe_public_article_url("https://example.com/story") assert allowed is False assert reason == "private_dns" def test_fetch_article_title_refuses_private_ip_without_request(self): with patch("services.geopolitics.requests.get") as mock_get: title = geopolitics._fetch_article_title("http://127.0.0.1/story") assert title is None mock_get.assert_not_called() class TestUpdaterHardening: def test_validate_update_url_allows_github_codeload(self): url = "https://codeload.github.com/BigBodyCobain/Shadowbroker/zip/refs/tags/v1.2.3" assert updater._validate_update_url(url) == url def test_validate_update_url_rejects_untrusted_host(self): with pytest.raises(RuntimeError, match="untrusted release host"): updater._validate_update_url("https://evil.example.com/update.zip") def test_extract_and_copy_rejects_zip_path_traversal(self, tmp_path): zip_path = tmp_path / "bad.zip" with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf: zf.writestr("../escape.txt", "nope") with pytest.raises(RuntimeError, match="path traversal entry"): updater._extract_and_copy(str(zip_path), str(tmp_path / "project"), str(tmp_path / "work")) def test_perform_update_returns_manual_url_on_failure(self, monkeypatch, tmp_path): def _boom(_temp_dir): raise RuntimeError("update exploded") monkeypatch.setattr(updater, "_download_release", _boom) result = updater.perform_update(str(tmp_path)) assert result["status"] == "error" assert result["manual_url"] == updater.GITHUB_RELEASES_PAGE_URL assert "update exploded" in result["message"] def test_perform_update_surfaces_release_metadata(self, monkeypatch, tmp_path): release_url = "https://github.com/BigBodyCobain/Shadowbroker/releases/tag/v1.2.3" download_url = ( "https://api.github.com/repos/BigBodyCobain/Shadowbroker/zipball/v1.2.3" ) backup_path = tmp_path / "backup.zip" (tmp_path / "frontend").mkdir() (tmp_path / "backend").mkdir() monkeypatch.setattr( updater, "_download_release", lambda _temp_dir: ("dummy.zip", "v1.2.3", download_url, release_url), ) monkeypatch.setattr(updater, "_validate_zip_hash", lambda _zip_path: None) monkeypatch.setattr(updater, "_backup_current", lambda *_args: str(backup_path)) monkeypatch.setattr(updater, "_extract_and_copy", lambda *_args: 42) result = updater.perform_update(str(tmp_path)) assert result["status"] == "ok" assert result["version"] == "v1.2.3" assert result["files_updated"] == 42 assert result["backup_path"] == str(backup_path) assert result["manual_url"] == release_url assert result["release_url"] == release_url assert result["download_url"] == download_url def test_perform_update_returns_manual_for_non_source_runtime(self, monkeypatch, tmp_path): release_url = "https://github.com/BigBodyCobain/Shadowbroker/releases/tag/v1.2.3" download_url = ( "https://api.github.com/repos/BigBodyCobain/Shadowbroker/zipball/v1.2.3" ) monkeypatch.setattr( updater, "_download_release", lambda _temp_dir: ("dummy.zip", "v1.2.3", download_url, release_url), ) result = updater.perform_update(str(tmp_path)) assert result["status"] == "manual" assert result["version"] == "v1.2.3" assert result["manual_url"] == release_url assert result["release_url"] == release_url assert result["download_url"] == download_url assert "does not support in-place source updates" in result["message"]