from datetime import datetime from pathlib import Path from types import SimpleNamespace from fastapi import FastAPI, HTTPException from fastapi.testclient import TestClient def _user(user_id: int, *, is_admin: bool = False): return SimpleNamespace(id=user_id, is_admin=is_admin) def _task(*, metadata=None, result=None, status="completed", task_type="pdf_generation"): return SimpleNamespace( task_id="task-1", task_type=task_type, status=SimpleNamespace(value=status), progress=100.0, created_at=datetime(2026, 4, 7, 12, 0, 0), updated_at=datetime(2026, 4, 7, 12, 1, 0), metadata=metadata or {}, result=result, error=None, ) def test_export_task_routes_require_authentication(): from landppt.web.route_modules import export_routes app = FastAPI() app.include_router(export_routes.router) app.dependency_overrides[export_routes.get_current_user_required] = lambda: (_ for _ in ()).throw( HTTPException(status_code=401, detail="Authentication required") ) client = TestClient(app) status_response = client.get("/api/landppt/tasks/task-1") download_response = client.get("/api/landppt/tasks/task-1/download") assert status_response.status_code == 401 assert download_response.status_code == 401 def test_export_task_status_hides_other_users_tasks(monkeypatch): from landppt.services import background_tasks as background_tasks_module from landppt.web.route_modules import export_routes class FakeTaskManager: async def get_task_async(self, task_id: str): return _task(metadata={"project_id": "proj-1", "user_id": 22}) monkeypatch.setattr(background_tasks_module, "get_task_manager", lambda: FakeTaskManager()) app = FastAPI() app.include_router(export_routes.router) app.dependency_overrides[export_routes.get_current_user_required] = lambda: _user(11, is_admin=False) client = TestClient(app) response = client.get("/api/landppt/tasks/task-1") assert response.status_code == 404 assert response.json()["detail"] == "Task not found" def test_export_task_status_hides_ownerless_tasks_from_non_admin(monkeypatch): from landppt.services import background_tasks as background_tasks_module from landppt.web.route_modules import export_routes class FakeTaskManager: async def get_task_async(self, task_id: str): return _task(metadata={"project_id": "proj-1"}) monkeypatch.setattr(background_tasks_module, "get_task_manager", lambda: FakeTaskManager()) app = FastAPI() app.include_router(export_routes.router) app.dependency_overrides[export_routes.get_current_user_required] = lambda: _user(11, is_admin=False) client = TestClient(app) response = client.get("/api/landppt/tasks/task-1") assert response.status_code == 404 assert response.json()["detail"] == "Task not found" def test_export_task_status_redacts_internal_paths(monkeypatch): from landppt.services import background_tasks as background_tasks_module from landppt.web.route_modules import export_routes class FakeTaskManager: async def get_task_async(self, task_id: str): return _task( metadata={ "project_id": "proj-1", "project_topic": "Demo", "user_id": 11, "pdf_path": "/tmp/private.pdf", "pptx_path": "/tmp/private.pptx", "progress_message": "working", }, result={ "success": True, "pdf_path": "/tmp/private.pdf", "pptx_path": "/tmp/private.pptx", "project_topic": "Demo", }, task_type="pdf_generation", ) monkeypatch.setattr(background_tasks_module, "get_task_manager", lambda: FakeTaskManager()) app = FastAPI() app.include_router(export_routes.router) app.dependency_overrides[export_routes.get_current_user_required] = lambda: _user(11, is_admin=False) client = TestClient(app) response = client.get("/api/landppt/tasks/task-1") assert response.status_code == 200 payload = response.json() assert payload["message"] == "working" assert payload["download_url"] == "/api/landppt/tasks/task-1/download" assert payload["metadata"] == { "project_id": "proj-1", "project_topic": "Demo", "user_id": 11, "progress_message": "working", } assert payload["result"] == { "success": True, "project_topic": "Demo", } def test_export_task_download_allows_admin_bypass(monkeypatch, tmp_path): from landppt.services import background_tasks as background_tasks_module from landppt.web.route_modules import export_routes pdf_path = tmp_path / "demo.pdf" pdf_path.write_bytes(b"%PDF-1.4\n") class FakeTaskManager: async def get_task_async(self, task_id: str): return _task( metadata={"project_id": "proj-1", "project_topic": "Demo"}, result={"success": True, "pdf_path": str(pdf_path)}, task_type="pdf_generation", ) monkeypatch.setattr(background_tasks_module, "get_task_manager", lambda: FakeTaskManager()) monkeypatch.setattr(background_tasks_module, "TaskStatus", SimpleNamespace(COMPLETED=_task().status)) app = FastAPI() app.include_router(export_routes.router) app.dependency_overrides[export_routes.get_current_user_required] = lambda: _user(1, is_admin=True) client = TestClient(app) response = client.get("/api/landppt/tasks/task-1/download") assert response.status_code == 200 assert response.content.startswith(b"%PDF-1.4") def test_export_task_route_source_includes_owner_scoping_changes(): source = Path("/root/clawd/src/landppt/web/route_modules/export_routes.py").read_text(encoding="utf-8") assert 'metadata_filter={"project_id": project_id, "user_id": user.id}' in source assert '"user_id": user.id' in source assert '"metadata": _sanitize_task_mapping(task.metadata)' in source assert 'response["result"] = _sanitize_task_mapping(task.result)' in source assert "_ensure_task_access(task, user)" in source def test_narration_task_route_source_includes_owner_scoping_changes(): source = Path("/root/clawd/src/landppt/web/route_modules/narration_routes.py").read_text(encoding="utf-8") assert 'metadata_filter={"project_id": project_id, "language": language, "provider": provider, "user_id": user.id}' in source assert '"user_id": user.id' in source