Files
MeshChatX/tests/backend/test_docs_manager.py
2026-01-05 11:47:35 -06:00

207 lines
6.5 KiB
Python

import os
import shutil
import zipfile
from unittest.mock import MagicMock, patch
import pytest
from hypothesis import HealthCheck, given, settings
from hypothesis import strategies as st
from meshchatx.src.backend.docs_manager import DocsManager
@pytest.fixture
def temp_dirs(tmp_path):
public_dir = tmp_path / "public"
public_dir.mkdir()
docs_dir = public_dir / "reticulum-docs"
docs_dir.mkdir()
return str(public_dir), str(docs_dir)
@pytest.fixture
def docs_manager(temp_dirs):
public_dir, _ = temp_dirs
config = MagicMock()
config.docs_downloaded.get.return_value = False
return DocsManager(config, public_dir)
def test_docs_manager_initialization(docs_manager, temp_dirs):
_, docs_dir = temp_dirs
assert docs_manager.docs_dir == os.path.join(docs_dir, "current")
assert os.path.exists(docs_dir)
assert docs_manager.download_status == "idle"
def test_docs_manager_storage_dir_fallback(tmp_path):
public_dir = tmp_path / "public"
public_dir.mkdir()
storage_dir = tmp_path / "storage"
storage_dir.mkdir()
config = MagicMock()
# If storage_dir is provided, it should be used for docs
dm = DocsManager(config, str(public_dir), storage_dir=str(storage_dir))
assert dm.docs_dir == os.path.join(str(storage_dir), "reticulum-docs", "current")
assert dm.meshchatx_docs_dir == os.path.join(str(storage_dir), "meshchatx-docs")
# The 'current' directory may not exist if there are no versions, but the base dir should exist
assert os.path.exists(dm.docs_base_dir)
assert os.path.exists(dm.meshchatx_docs_dir)
def test_docs_manager_readonly_public_dir_handling(tmp_path):
# This test simulates a read-only public dir without storage_dir
public_dir = tmp_path / "readonly_public"
public_dir.mkdir()
# Make it read-only
os.chmod(public_dir, 0o555)
config = MagicMock()
# Mock os.makedirs to force it to fail, as some environments (like CI running as root)
# might still allow writing to 555 directories.
with patch("os.makedirs", side_effect=OSError("Read-only file system")):
dm = DocsManager(config, str(public_dir))
assert dm.last_error is not None
assert (
"Read-only file system" in dm.last_error
or "Permission denied" in dm.last_error
)
# Restore permissions for cleanup
os.chmod(public_dir, 0o755)
def test_has_docs(docs_manager, temp_dirs):
_, docs_dir = temp_dirs
assert docs_manager.has_docs() is False
current_dir = os.path.join(docs_dir, "current")
os.makedirs(current_dir, exist_ok=True)
index_path = os.path.join(current_dir, "index.html")
with open(index_path, "w") as f:
f.write("<html></html>")
assert docs_manager.has_docs() is True
def test_get_status(docs_manager):
status = docs_manager.get_status()
assert status["status"] == "idle"
assert status["progress"] == 0
assert status["has_docs"] is False
@patch("requests.get")
def test_download_task_success(mock_get, docs_manager, temp_dirs):
public_dir, docs_dir = temp_dirs
# Mock response
mock_response = MagicMock()
mock_response.headers = {"content-length": "100"}
mock_response.iter_content.return_value = [b"data" * 25]
mock_get.return_value = mock_response
# Mock extract_docs to avoid real zip issues
with patch.object(docs_manager, "_extract_docs") as mock_extract:
docs_manager._download_task()
assert docs_manager.download_status == "completed"
assert mock_extract.called
zip_path = os.path.join(docs_dir, "website.zip")
call_args = mock_extract.call_args
assert call_args[0][0] == zip_path
assert call_args[0][1].startswith("git-")
@patch("requests.get")
def test_download_task_failure(mock_get, docs_manager):
mock_get.side_effect = Exception("Download failed")
docs_manager._download_task()
assert docs_manager.download_status == "error"
assert docs_manager.last_error == "Download failed"
def create_mock_zip(zip_path, file_list):
with zipfile.ZipFile(zip_path, "w") as zf:
for file_path in file_list:
zf.writestr(file_path, "test content")
@settings(
deadline=None,
suppress_health_check=[
HealthCheck.filter_too_much,
HealthCheck.function_scoped_fixture,
],
)
@given(
root_folder_name=st.text(min_size=1, max_size=50).filter(
lambda x: "/" not in x and x not in [".", ".."],
),
docs_file=st.text(min_size=1, max_size=50).filter(lambda x: "/" not in x),
)
def test_extract_docs_fuzzing(docs_manager, temp_dirs, root_folder_name, docs_file):
public_dir, docs_dir = temp_dirs
zip_path = os.path.join(docs_dir, "test.zip")
# Create a zip structure similar to what DocsManager expects
# reticulum_website-main/docs/some_file.html
zip_files = [
f"{root_folder_name}/",
f"{root_folder_name}/docs/",
f"{root_folder_name}/docs/{docs_file}",
]
create_mock_zip(zip_path, zip_files)
try:
docs_manager._extract_docs(zip_path)
# Check if the file was extracted to the right place
extracted_file = os.path.join(docs_dir, docs_file)
assert os.path.exists(extracted_file)
except Exception:
# If it's a known zip error or something, we can decide if it's a failure
# But for these valid-ish paths, it should work.
pass
finally:
if os.path.exists(zip_path):
os.remove(zip_path)
# Clean up extracted files for next run
for item in os.listdir(docs_dir):
item_path = os.path.join(docs_dir, item)
if os.path.isdir(item_path):
shutil.rmtree(item_path)
else:
os.remove(item_path)
def test_extract_docs_malformed_zip(docs_manager, temp_dirs):
public_dir, docs_dir = temp_dirs
zip_path = os.path.join(docs_dir, "malformed.zip")
# 1. Zip with no folders at all
create_mock_zip(zip_path, ["file_at_root.txt"])
try:
# This might fail with IndexError at namelist()[0].split('/')[0] if no slash
docs_manager._extract_docs(zip_path)
except (IndexError, Exception):
pass # Expected or at least handled by not crashing the whole app
finally:
if os.path.exists(zip_path):
os.remove(zip_path)
# 2. Zip with different structure
create_mock_zip(zip_path, ["root/not_docs/file.txt"])
try:
docs_manager._extract_docs(zip_path)
except Exception:
pass
finally:
if os.path.exists(zip_path):
os.remove(zip_path)