mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-13 02:12:21 +02:00
Merge verified Odysseus fixes
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
"""Issue #4593 — the CalDAV DAVClient must be closed on every path.
|
||||
|
||||
`_sync_blocking` (src/caldav_sync.py) and `_writeback_blocking`
|
||||
(src/caldav_writeback.py) each open their own DAVClient. The client holds an
|
||||
HTTP session with pooled connections; if it is never closed those connections
|
||||
leak for the lifetime of the process. These tests pin that the client is
|
||||
closed on the discovery early-returns, the normal return, and the
|
||||
write-back paths, using a fake client so no network or `caldav` install is
|
||||
needed.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import types
|
||||
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
|
||||
def _stub_sync_deps(monkeypatch):
|
||||
"""Make `_sync_blocking`'s lazy imports resolve without a real caldav/db."""
|
||||
err_mod = types.ModuleType("caldav.lib.error")
|
||||
|
||||
class AuthorizationError(Exception):
|
||||
pass
|
||||
|
||||
class NotFoundError(Exception):
|
||||
pass
|
||||
|
||||
err_mod.AuthorizationError = AuthorizationError
|
||||
err_mod.NotFoundError = NotFoundError
|
||||
monkeypatch.setitem(sys.modules, "caldav", types.ModuleType("caldav"))
|
||||
monkeypatch.setitem(sys.modules, "caldav.lib", types.ModuleType("caldav.lib"))
|
||||
monkeypatch.setitem(sys.modules, "caldav.lib.error", err_mod)
|
||||
|
||||
db_mod = types.ModuleType("core.database")
|
||||
db_mod.CalendarCal = MagicMock()
|
||||
db_mod.CalendarEvent = MagicMock()
|
||||
db_mod.CalendarDeletedEvent = MagicMock()
|
||||
db_mod.SessionLocal = MagicMock()
|
||||
if "core" not in sys.modules:
|
||||
monkeypatch.setitem(sys.modules, "core", types.ModuleType("core"))
|
||||
monkeypatch.setitem(sys.modules, "core.database", db_mod)
|
||||
|
||||
# Stub routes.calendar_routes so the lazy import of _ensure_positive_duration
|
||||
# inside _sync_blocking doesn't drag in dateutil / FastAPI / SQLAlchemy.
|
||||
routes_mod = types.ModuleType("routes")
|
||||
cal_routes_mod = types.ModuleType("routes.calendar_routes")
|
||||
cal_routes_mod._ensure_positive_duration = lambda start, end, all_day: end
|
||||
if "routes" not in sys.modules:
|
||||
monkeypatch.setitem(sys.modules, "routes", routes_mod)
|
||||
monkeypatch.setitem(sys.modules, "routes.calendar_routes", cal_routes_mod)
|
||||
|
||||
return AuthorizationError
|
||||
|
||||
|
||||
def test_sync_closes_client_on_discovery_auth_failure(monkeypatch):
|
||||
import src.caldav_sync as sync
|
||||
|
||||
AuthorizationError = _stub_sync_deps(monkeypatch)
|
||||
client = MagicMock()
|
||||
client.principal.side_effect = AuthorizationError("bad credentials")
|
||||
monkeypatch.setattr(sync, "_build_dav_client", lambda *a, **k: client)
|
||||
|
||||
result = sync._sync_blocking("alice", "https://dav.example.com/", "u", "p")
|
||||
|
||||
client.close.assert_called_once()
|
||||
assert any("Discovery failed" in e for e in result["errors"])
|
||||
|
||||
|
||||
def test_sync_closes_client_when_url_fallback_fails(monkeypatch):
|
||||
import src.caldav_sync as sync
|
||||
|
||||
_stub_sync_deps(monkeypatch)
|
||||
client = MagicMock()
|
||||
# principal() raises a generic error -> the URL-as-calendar fallback is
|
||||
# tried; make that fail too so the function hits the early return.
|
||||
client.principal.side_effect = RuntimeError("no principal endpoint")
|
||||
monkeypatch.setattr(sync, "_build_dav_client", lambda *a, **k: client)
|
||||
monkeypatch.setattr(
|
||||
sync, "_open_url_as_calendar",
|
||||
MagicMock(side_effect=RuntimeError("not a calendar")),
|
||||
)
|
||||
|
||||
result = sync._sync_blocking("alice", "https://dav.example.com/", "u", "p")
|
||||
|
||||
client.close.assert_called_once()
|
||||
assert result["errors"]
|
||||
|
||||
|
||||
def test_writeback_closes_client_when_no_calendars(monkeypatch):
|
||||
import src.caldav_sync as sync
|
||||
import src.caldav_writeback as wb
|
||||
|
||||
client = MagicMock()
|
||||
monkeypatch.setattr(sync, "_build_dav_client", lambda *a, **k: client)
|
||||
monkeypatch.setattr(wb, "_discover_calendars", lambda c: [])
|
||||
|
||||
result = wb._writeback_blocking(
|
||||
"caldav-1", {"uid": "evt-1"}, False, "https://dav.example.com/", "u", "p"
|
||||
)
|
||||
|
||||
client.close.assert_called_once()
|
||||
assert result["ok"] is False
|
||||
|
||||
|
||||
def test_writeback_closes_client_on_success(monkeypatch):
|
||||
import src.caldav_sync as sync
|
||||
import src.caldav_writeback as wb
|
||||
|
||||
client = MagicMock()
|
||||
monkeypatch.setattr(sync, "_build_dav_client", lambda *a, **k: client)
|
||||
monkeypatch.setattr(wb, "_discover_calendars", lambda c: [MagicMock()])
|
||||
monkeypatch.setattr(wb, "push_event", lambda *a, **k: {"ok": True})
|
||||
|
||||
result = wb._writeback_blocking(
|
||||
"caldav-1", {"uid": "evt-1"}, False, "https://dav.example.com/", "u", "p"
|
||||
)
|
||||
|
||||
client.close.assert_called_once()
|
||||
assert result["ok"] is True
|
||||
|
||||
|
||||
def test_sync_closes_client_when_session_local_raises(monkeypatch):
|
||||
import src.caldav_sync as sync
|
||||
|
||||
AuthorizationError = _stub_sync_deps(monkeypatch)
|
||||
|
||||
# Give principal() a working response so discovery passes
|
||||
mock_principal = MagicMock()
|
||||
mock_cal = MagicMock()
|
||||
mock_cal.url = "https://dav.example.com/alice/home/"
|
||||
mock_principal.calendars.return_value = [mock_cal]
|
||||
|
||||
client = MagicMock()
|
||||
client.principal.return_value = mock_principal
|
||||
monkeypatch.setattr(sync, "_build_dav_client", lambda *a, **k: client)
|
||||
|
||||
# Make SessionLocal blow up before any DB work
|
||||
import sys
|
||||
sys.modules["core.database"].SessionLocal.side_effect = RuntimeError("DB unavailable")
|
||||
|
||||
with pytest.raises(RuntimeError, match="DB unavailable"):
|
||||
sync._sync_blocking("alice", "https://dav.example.com/", "u", "p")
|
||||
|
||||
client.close.assert_called_once()
|
||||
Reference in New Issue
Block a user